summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--.gitmodules3
-rw-r--r--cmake/mcpc.cmake6
-rw-r--r--docs/implementation-status.md34
-rw-r--r--scripts/python/README.md121
-rw-r--r--scripts/python/benchmark.py862
-rw-r--r--scripts/python/benchmark_results.json1221
-rw-r--r--scripts/python/benchmark_results.md85
-rw-r--r--scripts/python/p0_regression.py107
-rw-r--r--src/mcp/server.c4
-rw-r--r--src/mcp/tools/common/common.c7
-rw-r--r--src/mcp/tools/common/common.h3
-rw-r--r--src/mcp/tools/delete_message.c10
-rw-r--r--src/mcp/tools/read_messages.c2
-rw-r--r--src/mcp/tools/search_messages.c21
-rw-r--r--src/mcp/tools/write_message.c17
-rw-r--r--tests/CMakeLists.txt20
-rw-r--r--tests/mcp_protocol_test.py236
-rw-r--r--tests/mcpc_test.c41
m---------third_party/mcpc0
-rw-r--r--third_party/mcpc/.github/workflows/mcpc.yml112
-rw-r--r--third_party/mcpc/.gitignore30
-rw-r--r--third_party/mcpc/.indent.pro1
-rw-r--r--third_party/mcpc/CMakeLists.txt10
-rw-r--r--third_party/mcpc/Makefile51
-rw-r--r--third_party/mcpc/README.md474
-rw-r--r--third_party/mcpc/README.org197
-rw-r--r--third_party/mcpc/UPSTREAM.md12
-rw-r--r--third_party/mcpc/licenses/LICENSE-mjson.txt21
-rw-r--r--third_party/mcpc/mcpc/_c23_keywords.h30
-rw-r--r--third_party/mcpc/mcpc/_c23_stdckdint.h28
-rw-r--r--third_party/mcpc/mcpc/_c23_uchar.h31
-rw-r--r--third_party/mcpc/mcpc/anydata.h101
-rw-r--r--third_party/mcpc/mcpc/errcode.h50
-rw-r--r--third_party/mcpc/mcpc/mcpc.h33
-rw-r--r--third_party/mcpc/mcpc/prmpt.h98
-rw-r--r--third_party/mcpc/mcpc/rsc.h60
-rw-r--r--third_party/mcpc/mcpc/server.h89
-rw-r--r--third_party/mcpc/mcpc/tool.h85
-rw-r--r--third_party/mcpc/mcpc/ucbr.h55
-rw-r--r--third_party/mcpc/mcpc/visb.h38
-rw-r--r--third_party/mcpc/misc/api.md1112
-rw-r--r--third_party/mcpc/misc/api.org1193
-rw-r--r--third_party/mcpc/misc/init-msvc.bat5
-rw-r--r--third_party/mcpc/mk/ccdefs-cygwin.mk34
-rw-r--r--third_party/mcpc/mk/ccdefs-linux.mk79
-rw-r--r--third_party/mcpc/mk/ccdefs-macos.mk53
-rw-r--r--third_party/mcpc/mk/ccdefs-mingw.mk89
-rw-r--r--third_party/mcpc/mk/ccdefs-win.mk60
-rw-r--r--third_party/mcpc/mk/dbgrel.mk92
-rw-r--r--third_party/mcpc/mk/step0.mk31
-rw-r--r--third_party/mcpc/src/.indent.pro1
-rw-r--r--third_party/mcpc/src/CMakeLists.txt32
-rw-r--r--third_party/mcpc/src/Makefile94
-rw-r--r--third_party/mcpc/src/alloc.c57
-rw-r--r--third_party/mcpc/src/alloc.h39
-rw-r--r--third_party/mcpc/src/anydata.c817
-rw-r--r--third_party/mcpc/src/anydata.h225
-rw-r--r--third_party/mcpc/src/complt.c162
-rw-r--r--third_party/mcpc/src/complt.h60
-rw-r--r--third_party/mcpc/src/errcode.c39
-rw-r--r--third_party/mcpc/src/errcode.h74
-rw-r--r--third_party/mcpc/src/ext_stdint.h54
-rw-r--r--third_party/mcpc/src/ext_string.h75
-rw-r--r--third_party/mcpc/src/log.c46
-rw-r--r--third_party/mcpc/src/log.h60
-rw-r--r--third_party/mcpc/src/mcpc.pc10
-rw-r--r--third_party/mcpc/src/mjson.c1372
-rw-r--r--third_party/mcpc/src/mjson.h296
-rw-r--r--third_party/mcpc/src/prmpt.c378
-rw-r--r--third_party/mcpc/src/prmpt.h80
-rw-r--r--third_party/mcpc/src/retbuf.c119
-rw-r--r--third_party/mcpc/src/retbuf.h50
-rw-r--r--third_party/mcpc/src/rsc.c177
-rw-r--r--third_party/mcpc/src/rsc.h51
-rw-r--r--third_party/mcpc/src/serlz.c619
-rw-r--r--third_party/mcpc/src/serlz.h62
-rw-r--r--third_party/mcpc/src/server.c1830
-rw-r--r--third_party/mcpc/src/server.h196
-rw-r--r--third_party/mcpc/src/tool.c505
-rw-r--r--third_party/mcpc/src/tool.h150
-rw-r--r--third_party/mcpc/src/ucbr.c288
-rw-r--r--third_party/mcpc/src/ucbr.h61
-rw-r--r--third_party/mcpc/src/ver.txt1
-rw-r--r--third_party/mcpc/tst/.indent.pro1
-rw-r--r--third_party/mcpc/tst/CMakeLists.txt33
-rw-r--r--third_party/mcpc/tst/Makefile108
-rw-r--r--third_party/mcpc/tst/commtst.c181
-rw-r--r--third_party/mcpc/tst/commtst.h147
-rw-r--r--third_party/mcpc/tst/itst-svcalc-req-promptslist-1.json1
-rw-r--r--third_party/mcpc/tst/itst-svcalc-req-resourceslist-1.json1
-rw-r--r--third_party/mcpc/tst/itst-svcalc-req-toolslist-1.json1
-rw-r--r--third_party/mcpc/tst/itst-svcalc.c207
93 files changed, 15874 insertions, 45 deletions
diff --git a/.gitignore b/.gitignore
index 7b6de5d..82b0bc7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,11 @@ install_manifest.txt
*.profraw
*.profdata
+# Python local environments and bytecode
+.venv/
+__pycache__/
+*.pyc
+
# IDE and editor state
/.vscode/
/.vs/
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index a9d75dc..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "third_party/mcpc"]
- path = third_party/mcpc
- url = https://github.com/micl2e2/mcpc.git
diff --git a/cmake/mcpc.cmake b/cmake/mcpc.cmake
index 8f396a7..384ada9 100644
--- a/cmake/mcpc.cmake
+++ b/cmake/mcpc.cmake
@@ -2,12 +2,12 @@ set(TINY_CONVMEM_MCPC_ROOT "${PROJECT_SOURCE_DIR}/third_party/mcpc")
if(NOT EXISTS "${TINY_CONVMEM_MCPC_ROOT}/mcpc/mcpc.h")
message(FATAL_ERROR
- "mcpc is not initialized. Run: git submodule update --init --recursive"
+ "The vendored mcpc fork is missing from third_party/mcpc"
)
endif()
-# The upstream CMake target at the pinned revision is incomplete. Keep this
-# source list aligned with mcpc/src/Makefile and do not modify the submodule.
+# The upstream CMake target at the imported revision is incomplete. This source
+# list defines the project-maintained vendored fork build.
set(TINY_CONVMEM_MCPC_SOURCES
"${TINY_CONVMEM_MCPC_ROOT}/src/alloc.c"
"${TINY_CONVMEM_MCPC_ROOT}/src/log.c"
diff --git a/docs/implementation-status.md b/docs/implementation-status.md
index 80c8f96..71bd226 100644
--- a/docs/implementation-status.md
+++ b/docs/implementation-status.md
@@ -1,6 +1,6 @@
# 設計・実装・テスト状況一覧
-最終更新日: 2026-06-29
+最終更新日: 2026-06-30
この文書は、[architecture.md](architecture.md) の初期案、確定したプロトタイプ実装計画、
現在のソースコード、CTestを突き合わせた追跡表です。設計案に存在しても確定したv1の
@@ -22,11 +22,14 @@
| 構成 | ビルド | CTest | 実施日 | 結果 |
|---|---|---|---|---|
-| Windows x64 / MSVC / Debug | ✅ 実施済 | ✅ 実施済 | 2026-06-29 | 12/12成功 |
-| Windows x64 / MSVC / Release | ✅ 実施済 | ✅ 実施済 | 2026-06-29 | 12/12成功 |
-| Windows x64 / MSVC / ASan | ✅ 実施済 | ✅ 実施済 | 2026-06-29 | 12/12成功 |
+| Windows x64 / MSVC / Debug | ✅ 実施済 | ✅ 実施済 | 2026-06-30 | 14/14成功 |
+| Windows x64 / MSVC / Release | ✅ 実施済 | ✅ 実施済 | 2026-06-30 | 14/14成功 |
+| Windows x64 / MSVC / ASan | ✅ 実施済 | ✅ 実施済 | 2026-06-30 | 14/14成功 |
| Windows以外 | ⬜ 未実施 | ⬜ 未実施 | ― | v1対象外 |
+Python MCP SDK 1.28.1によるglobal/scoped検索1000回交互呼出しも、Debug・Release・ASanの
+各構成で成功し、接続切断・異常終了・ASan診断がないことを確認しました。
+
実行コマンド:
```powershell
@@ -57,17 +60,17 @@ ctest --preset windows-vs2026-asan
| 設計項目 | 実装 | 実装根拠・現在の挙動 | テスト作成 | テスト実施 | 補足・残作業 |
|---|---|---|---|---|---|
-| `initialize` / `tools/list` / `tools/call` | ✅ 実装済 | MCP 2024-11-05の必要フローをローカルdispatcherで処理 | ✅ 作成済 | ✅ Debug/Release | MCP全メソッド対応ではない |
-| `notifications/initialized` | ✅ 実装済 | notificationとして応答せず継続 | 🟡 smokeに入力あり | ✅ Debug/Release | 単独の応答なし検証は未作成 |
-| raw JSON arguments | ✅ 実装済 | mcpc同梱mjsonでobjectを直接解析 | ✅ 作成済 | ✅ Debug/Release | malformed JSON/JSON-RPCの網羅試験は未作成 |
-| required/optional、object、array schema | ✅ 実装済 | `tools/list` に正しいschemaを返す | ✅ 作成済 | ✅ Debug/Release | schema全文のgolden testではなく主要断片を検証 |
+| `initialize` / `tools/list` / `tools/call` | ✅ 実装済 | MCP 2024-11-05をvendored MCPC forkで処理 | ✅ 作成済 | ✅ Debug/Release/ASan | MCP全メソッド対応ではない |
+| `notifications/initialized` | ✅ 実装済 | notificationとして応答せず継続 | ✅ 作成済 | ✅ Debug/Release/ASan | 次requestのIDで応答なしを検証 |
+| raw JSON arguments | ✅ 実装済 | mcpc同梱mjsonでobjectを直接解析 | ✅ 作成済 | ✅ Debug/Release/ASan | malformed JSON、型不正、欠落を検証 |
+| required/optional schema | ✅ 実装済 | schemaとruntimeで同じrequired属性を使用 | ✅ 作成済 | ✅ Debug/Release/ASan | 整数は`integer`として広告 |
| 64-bit timestamp/counter | ✅ 実装済 | JSON builderとバイナリ形式で64-bitを使用 | 🟡 間接テスト | ✅ Debug/Release | 最大/最小境界値は未作成 |
| callbackへのアプリ状態受渡し | ✅ 実装済 | server/handlerへStoreとSearch Indexを明示的に渡す | 🟡 間接テスト | ✅ Debug/Release | 汎用callback APIではない |
-| upstream `server.c`/serializerのoverlay | ➖ 設計変更 | submoduleを変更せず、プロジェクト側dispatcherを実装 | ✅ 統合テスト | ✅ Debug/Release | 固定版mcpcのmjsonのみ利用する方針へ変更 |
+| MCPC依存管理 | ✅ vendored fork化 | submoduleを廃止し`third_party/mcpc`を親repositoryで追跡 | ✅ 単体・統合 | ✅ Debug/Release/ASan | upstream基準commitを`UPSTREAM.md`へ記録 |
| TextContent内JSON成功応答 | ✅ 実装済 | 全ツール結果をescape済みJSON文字列で返す | ✅ 作成済 | ✅ Debug/Release | `structuredContent` は未対応 |
| MCP application error (`isError=true`) | ✅ 実装済 | 検証/I/O/内部エラーで設定 | ✅ 検証エラー作成済 | ✅ Debug/Release | 全エラーコードのMCP経由試験は未作成 |
| 安定エラーコード | 🟡 部分実装 | `VALIDATION_ERROR`, `IO_ERROR`, `CORRUPT_STORE`, `BUSY`, `INTERNAL_ERROR` を定義・使用 | 🟡 一部作成済 | 🟡 一部実施済 | `BUSY`/`CORRUPT_STORE` は起動前に発生し、MCP応答にはならない |
-| 不正JSON-RPCへの標準error object | ⬜ 未実装 | 一部の不正requestは応答せず無視する | ⬜ 未作成 | ⬜ 未実施 | parse error、invalid request、method not foundの標準応答が必要 |
+| 不正JSON-RPCへの標準error object | ✅ 実装済 | `-32700/-32600/-32601/-32602/-32603`をJSON-RPC 2.0形式で返す | ✅ 作成済 | ✅ Debug/Release/ASan | Python SDK相互運用も確認 |
## MCPツール別状況
@@ -84,11 +87,11 @@ ctest --preset windows-vs2026-asan
| timestamp、message_id安定順序 | ✅ 実装済 | ⬜ 未作成 | ⬜ 未実施 | 複数件・同一timestampの順序試験が必要 |
| `search_messages` content検索 | ✅ 実装済 | ✅ 作成済 | ✅ Debug/Release | ASCII大小文字とMCP検索を確認 |
| 日本語・tag検索 | ✅ 実装済 | ✅ 作成済 | ✅ Debug/Release | Domainで確認 |
-| `session_id` 絞込み | ✅ 実装済 | ⬜ 未作成 | ⬜ 未実施 | 複数sessionを使う統合試験が必要 |
-| `limit` 既定20・最大100 | ✅ 実装済 | 🟡 一部作成済 | ✅ Debug/Release | limit=10は使用。既定値、0、100、101、非整数境界は未確認 |
+| `session_id` 絞込み | ✅ 実装済 | ✅ 作成済 | ✅ Debug/Release/ASan | 2 sessionでglobal/scopedを1000回交互に確認 |
+| `limit` 既定20・最大100 | ✅ 実装済 | ✅ 作成済 | ✅ Debug/Release/ASan | 省略、0、101、少数拒否を統合試験で確認 |
| 検索順位とtie-break | ✅ 実装済 | 🟡 基本scoreのみ | 🟡 一部実施済 | 複数候補で exact match、timestamp、ID順を検証していない |
| `delete_message` 即時不可視化 | ✅ 実装済 | ✅ 作成済 | ✅ Debug/Release | lifecycle testでdelete後のreadから消えることを確認 |
-| `delete_message` session照合 | ✅ 実装済 | 🟡 正しいsessionのみ | 🟡 一部実施済 | session不一致時のNOT_FOUND未確認 |
+| `delete_message` session照合 | ✅ 実装済 | 🟡 optional省略を確認 | 🟡 一部実施済 | session不一致時のNOT_FOUND未確認 |
| `delete_message` NOT_FOUND | ✅ 実装済 | ⬜ 未作成 | ⬜ 未実施 | 存在しないID、二重削除の統合試験が必要 |
| `rebuild_index` 同期compaction | ✅ 実装済 | ✅ 作成済 | ✅ Debug/Release | MCP呼出しと削除本文の物理除去を確認 |
| rebuild結果カウンタ | ✅ 実装済 | ✅ 作成済 | ✅ Debug/Release | `records_removed=1` を確認 |
@@ -197,6 +200,8 @@ ctest --preset windows-vs2026-asan
|---|---|---|---|---|---|
| `tiny-convmem-mcp.smoke` | MCP E2E | ✅ 作成済 | ✅ 成功 | ✅ 成功 | initialize、6 tool schema、write/read/search/rebuild/health、検証エラー |
| `tiny-convmem-mcp.domain` | Unit | ✅ 作成済 | ✅ 成功 | ✅ 成功 | role/tier、UTF-8、ULID、deep copy、ASCII/日本語n-gram |
+| `tiny-convmem-mcp.mcpc` | Unit | ✅ 作成済 | ✅ 成功 | ✅ 成功 | required属性、NULL callback拒否、fork API |
+| `tiny-convmem-mcp.protocol` | MCP E2E | ✅ 作成済 | ✅ 成功 | ✅ 成功 | JSON-RPC異常系、schema/default、1000回交互検索、終了診断 |
| `tiny-convmem-mcp.storage` | Unit/Integration | ✅ 作成済 | ✅ 成功 | ✅ 成功 | lock、write/reopen、ULID順、temp失敗、CRC破損拒否 |
| `tiny-convmem-mcp.lifecycle` | MCP/Storage E2E | ✅ 作成済 | ✅ 成功 | ✅ 成功 | delete、不可視化、compaction、本文除去、不正snapshot、末尾復旧 |
| `tiny-convmem-mcp.requires-data-dir` | Process | ✅ 作成済 | ✅ 成功 | ✅ 成功 | data-dir必須、非0終了 |
@@ -215,9 +220,8 @@ ctest --preset windows-vs2026-asan
| P2 | read/searchの安定順序 | 🟡 実装済・試験不足 | 複数session/同時刻/同score/limitを網羅 |
| P2 | compaction crash/failure matrix | 🟡 一部のみ | flush、rename前後、log/snapshot間の各停止点から復旧 |
| P2 | 4 GiB超offset | ⬜ 未作成・未実施 | sparse logで64-bit seek/replayを確認 |
-| P2 | JSON-RPC標準エラー | ⬜ 未実装 | malformed/invalid/method-not-foundへ規格準拠errorを返す |
| P3 | ログサイズ閾値compaction | ⬜ 未実装 | 設定閾値超過時に安全に同期compactionする |
-| P3 | 性能・メモリ・長時間試験 | ⬜ 未作成・未実施 | 大量message、巨大session、繰返しrebuildの基準値を定義・測定 |
+| P3 | 性能・メモリ試験 | 🟡 長時間接続のみ実装 | 大量message、巨大session、繰返しrebuildの基準値を定義・測定 |
## 明示的なv1対象外
diff --git a/scripts/python/README.md b/scripts/python/README.md
new file mode 100644
index 0000000..7018313
--- /dev/null
+++ b/scripts/python/README.md
@@ -0,0 +1,121 @@
+# tiny-convmem-mcp ユーザ視点負荷テスト
+
+このディレクトリには、[Model Context Protocol (MCP) Python SDK](https://github.com/modelcontextprotocol/python-sdk) を使った、Windows 版 `tiny-convmem-mcp` サーバのエンドツーエンド負荷テスト&ベンチマークスクリプトがあります。
+
+## 環境
+
+`scripts/python/.venv` は uv で構築済みで、以下が含まれています。
+
+- Python 3.12.9
+- `mcp` 1.28.1 (Python SDK)
+
+必要に応じて追加インストールする場合:
+
+```powershell
+uv pip install --python .venv\Scripts\python.exe mcp
+```
+
+## 実行方法
+
+最新の Release ビルドを自動検出して実行します。
+
+```powershell
+cd scripts\python
+..\.venv\Scripts\python.exe benchmark.py
+```
+
+オプション:
+
+| オプション | 説明 |
+|---|---|
+| `--server <path>` | `tiny-convmem-mcp.exe` のパスを明示 |
+| `--iterations N` | 各シナリオの反復回数(デフォルト 100、rebuild/recovery は最大 5) |
+| `--warmup N` | 未使用(後方互換) |
+| `--output <path>` | 出力 JSON(デフォルト `benchmark_results.json`) |
+
+例:
+
+```powershell
+.venv\Scripts\python.exe benchmark.py --server ..\..\build\windows-vs2026\src\Release\tiny-convmem-mcp.exe --iterations 100 --output mybench.json
+```
+
+## 測定内容
+
+すべての操作は stdio MCP プロトコル経由で実際のサーバプロセスに対して行います。
+
+1. **write_message** — 小 (~20B)、中 (1KiB)、大 (32KiB) content サイズ別
+2. **write_message (with tags)** — `metadata_json` 文字列に tags / `memory_tier` を含む書き込み
+3. **read_messages** — 100 件のメッセージを読み出し、件数を検証
+4. **search_messages** — 同じデータ・同じ query で global / session-scoped を比較
+5. **delete_message** — `message_id` 指定削除
+6. **rebuild_index** — compaction + snapshot 再生成(5 回反復)
+7. **get_system_health** — ストレージ健全性取得
+8. **recovery** — プロセス再起動後の復旧を 3 段階で計測
+ - restart + count check(1 回)
+ - restart + small read(5 回)
+ - restart + full read 100 msgs(5 回)
+
+各シナリオは独立した temp data directory で実行され、シナリオ間の状態累積を排除しています。
+
+## 出力
+
+- コンソール: p50/p95/p99/mean レイテンシと ops/sec のサマリ表
+- `<output>.json`: 詳細な計測結果(全サンプル含む)
+- `<output>.md`: Markdown レポート
+
+## 結果の見方
+
+```text
+=== tiny-convmem-mcp 負荷テスト結果 ===
+server : C:\...\Release\tiny-convmem-mcp.exe
+exe_mtime : 2026-06-30 01:46:57
+iterations : 100
+
+scenario succ fail p50[ms] p95[ms] p99[ms] mean[ms] ops/s
+----------------------------------------------------------------------------------------------------------------
+write_message (small ~20B) 100 0 0.399 1.009 1.718 0.505 1981.27
+...
+```
+
+## 注意
+
+- `tiny-convmem-mcp` は単一stdio・単一スレッドサーバのため、リクエストは逐次送信されます。並行負荷テストは対象外です。
+- 計測値には子プロセス起動、JSON シリアライズ、OS I/O、同期耐久性フラッシュ(fsync/FlushFileBuffers)が含まれます。
+- 各シナリオ内の反復は同一プロセス・同一セッションで実行します。したがって `write_message` の大サイズ計測など、前の反復がストレージ状態を変化させる可能性があります。
+
+## 参考: MCP 準拠状況
+
+本テストで確認できる MCP 機能:
+
+- `initialize` / `notifications/initialized`
+- `tools/list`(schema 取得)
+- `tools/call`(明示登録された6ツール)
+- TextContent 内 JSON 応答
+- `isError=true` によるアプリケーションレベルエラー
+
+確認済みのP0契約:
+
+- `write_message.metadata_json` は任意のJSON文字列で、省略時は `{}`
+- `search_messages.session_id` は任意で、省略時はglobal検索
+- `search_messages.limit` は任意で、既定20、整数1~100
+- `delete_message.session_id` は任意
+- `inputSchema.required` とruntimeの必須条件が一致
+- 成功・エラーの全応答がJSON-RPC 2.0形式で、Python MCP SDKから解釈可能
+- `read_messages` は現状フル履歴を返し、paginationは未実装
+
+未対応・確認していない項目:
+
+- `structuredContent` / 出力 schema
+- `resources/*`, `prompts/*`, `sampling/*`, `roots/*`
+- 並行リクエスト / 複数クライアント
+
+## P0回帰テスト
+
+Python MCP SDKでglobal/session-scoped検索を同一接続上で1000回交互に実行できます。
+
+```powershell
+.venv\Scripts\python.exe p0_regression.py --server ..\..\build\windows-vs2026\src\Release\tiny-convmem-mcp.exe
+```
+
+従来の約254リクエストでの切断は、Windowsの符号なし`SOCKET`でstdio sentinelを
+ネットワーク接続と誤判定し、connection poolを消費していたことが原因でした。
diff --git a/scripts/python/benchmark.py b/scripts/python/benchmark.py
new file mode 100644
index 0000000..69219c9
--- /dev/null
+++ b/scripts/python/benchmark.py
@@ -0,0 +1,862 @@
+#!/usr/bin/env python3
+"""
+tiny-convmem-mcp ユーザ視点負荷テスト&ベンチマーク
+
+MCP Python SDK (mcp.client.stdio + mcp.client.session) を使い、
+実際に tiny-convmem-mcp サーバプロセスを stdio 経由で起動し、
+エンドツーエンドのレイテンシとスループットを測定します。
+
+ポイント:
+- 各シナリオは独立した temp data directory で実行(状態累積を排除)
+- write_message は `metadata_json` 文字列を使用
+- 応答の isError / status / 件数を検証
+- rebuild_index は 5 回反復
+"""
+
+from __future__ import annotations
+
+import argparse
+import asyncio
+import json
+import os
+import random
+import shutil
+import statistics
+import subprocess
+import sys
+import tempfile
+import time
+from dataclasses import asdict, dataclass, field
+from pathlib import Path
+from typing import Any, Awaitable, Callable
+
+from mcp.client.session import ClientSession
+from mcp.client.stdio import StdioServerParameters, stdio_client
+from mcp.types import CallToolResult
+
+# ---------------------------------------------------------------------------
+# 定数・デフォルト
+# ---------------------------------------------------------------------------
+DEFAULT_ITERATIONS = 100
+DEFAULT_CONTENT_MEDIUM = 1024 # bytes
+DEFAULT_CONTENT_LARGE = 32 * 1024 # bytes
+WARMUP_COUNT = 5
+REBUILD_ITERATIONS = 5
+
+ROOT = Path(__file__).resolve().parent.parent.parent
+DEFAULT_RELEASE_EXE = (
+ ROOT / "build" / "windows-vs2026" / "src" / "Release" / "tiny-convmem-mcp.exe"
+)
+
+
+# ---------------------------------------------------------------------------
+# データクラス
+# ---------------------------------------------------------------------------
+@dataclass
+class LatencySample:
+ operation: str
+ duration_ms: float
+ success: bool
+ error: str | None = None
+ bytes_in: int = 0
+ bytes_out: int = 0
+
+
+@dataclass
+class ScenarioResult:
+ name: str
+ iterations: int
+ successes: int
+ failures: int
+ samples: list[float] = field(repr=False, default_factory=list)
+ throughput_ops_per_sec: float = 0.0
+ latency_p50_ms: float = 0.0
+ latency_p95_ms: float = 0.0
+ latency_p99_ms: float = 0.0
+ latency_min_ms: float = 0.0
+ latency_max_ms: float = 0.0
+ latency_mean_ms: float = 0.0
+ total_duration_ms: float = 0.0
+ total_bytes_in: int = 0
+ total_bytes_out: int = 0
+ notes: str = ""
+
+
+@dataclass
+class BenchmarkReport:
+ server: str
+ build_type: str
+ exe_mtime: str
+ iterations: int
+ warmup: int
+ scenarios: list[ScenarioResult] = field(default_factory=list)
+ health_after: dict[str, Any] | None = None
+ schema_check: dict[str, Any] | None = None
+ errors: list[str] = field(default_factory=list)
+
+ def as_dict(self) -> dict[str, Any]:
+ return asdict(self)
+
+
+# ---------------------------------------------------------------------------
+# ユーティリティ
+# ---------------------------------------------------------------------------
+def percentile(sorted_values: list[float], p: float) -> float:
+ if not sorted_values:
+ return 0.0
+ n = len(sorted_values)
+ if n == 1:
+ return sorted_values[0]
+ k = (n - 1) * p / 100.0
+ f = int(k)
+ c = min(f + 1, n - 1)
+ if f == c:
+ return sorted_values[f]
+ return sorted_values[f] + (sorted_values[c] - sorted_values[f]) * (k - f)
+
+
+def summarize(name: str, samples: list[LatencySample]) -> ScenarioResult:
+ ok = [s for s in samples if s.success]
+ failed = [s for s in samples if not s.success]
+ durations = sorted(s.duration_ms for s in ok)
+ result = ScenarioResult(
+ name=name,
+ iterations=len(samples),
+ successes=len(ok),
+ failures=len(failed),
+ samples=durations,
+ total_duration_ms=sum(s.duration_ms for s in samples),
+ total_bytes_in=sum(s.bytes_in for s in ok),
+ total_bytes_out=sum(s.bytes_out for s in ok),
+ notes=failed[0].error if failed else "",
+ )
+ if durations:
+ result.latency_min_ms = durations[0]
+ result.latency_max_ms = durations[-1]
+ result.latency_mean_ms = statistics.mean(durations)
+ result.latency_p50_ms = percentile(durations, 50)
+ result.latency_p95_ms = percentile(durations, 95)
+ result.latency_p99_ms = percentile(durations, 99)
+ if result.total_duration_ms > 0 and ok:
+ result.throughput_ops_per_sec = len(ok) / (result.total_duration_ms / 1000.0)
+ return result
+
+
+def text_content(result: CallToolResult) -> str:
+ if result.isError:
+ raise RuntimeError(f"tool returned isError: {result.content!r}")
+ if not result.content:
+ return ""
+ return "".join(item.text for item in result.content if item.type == "text")
+
+
+def json_result(result: CallToolResult) -> dict:
+ text = text_content(result)
+ payload = json.loads(text)
+ return payload
+
+
+def random_content(size: int) -> str:
+ alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "
+ return "".join(random.choice(alphabet) for _ in range(size))
+
+
+def make_metadata_json(tags: list[str] | None = None, tier: str = "short") -> str:
+ obj: dict[str, Any] = {"memory_tier": tier}
+ if tags:
+ obj["tags"] = tags
+ return json.dumps(obj, separators=(",", ":"))
+
+
+# ---------------------------------------------------------------------------
+# クライアント・サーバランチャー
+# ---------------------------------------------------------------------------
+class ServerContext:
+ """MCP stdio サーバを起動し、ClientSession でラップするコンテキスト。"""
+
+ def __init__(self, exe: Path, data_dir: Path) -> None:
+ self.exe = exe
+ self.data_dir = data_dir
+ self.read_stream = None
+ self.write_stream = None
+ self.session = None
+
+ async def __aenter__(self) -> ClientSession:
+ params = StdioServerParameters(
+ command=str(self.exe),
+ args=["--data-dir", str(self.data_dir)],
+ env={**os.environ},
+ )
+ self._ctx = stdio_client(params)
+ self.read_stream, self.write_stream = await self._ctx.__aenter__()
+ self.session = ClientSession(self.read_stream, self.write_stream)
+ await self.session.__aenter__()
+ await self.session.initialize()
+ return self.session
+
+ async def __aexit__(self, exc_type, exc, tb) -> None:
+ if self.session:
+ session = self.session
+ self.session = None
+ await session.__aexit__(exc_type, exc, tb)
+ await self._ctx.__aexit__(exc_type, exc, tb)
+
+
+async def with_session(exe: Path, data_dir: Path) -> Awaitable[ClientSession]:
+ return await ServerContext(exe, data_dir).__aenter__()
+
+
+# ---------------------------------------------------------------------------
+# 操作ラッパー(結果検証付き)
+# ---------------------------------------------------------------------------
+async def write_message(
+ session: ClientSession,
+ session_id: str,
+ content: str,
+ metadata_json: str = '{"memory_tier":"short"}',
+) -> dict:
+ result = await session.call_tool(
+ "write_message",
+ {
+ "session_id": session_id,
+ "role": "user",
+ "content": content,
+ "metadata_json": metadata_json,
+ },
+ )
+ payload = json_result(result)
+ if payload.get("status") != "OK":
+ raise RuntimeError(f"write failed: {payload}")
+ msg = payload.get("message", {})
+ if not msg.get("message_id"):
+ raise RuntimeError("write response missing message_id")
+ return msg
+
+
+async def read_messages(session: ClientSession, session_id: str) -> list[dict]:
+ result = await session.call_tool("read_messages", {"session_id": session_id})
+ payload = json_result(result)
+ history = payload.get("history", [])
+ if not isinstance(history, list):
+ raise RuntimeError("history is not a list")
+ return history
+
+
+async def search_messages(
+ session: ClientSession, query: str, session_id: str, limit: int = 10
+) -> list[dict]:
+ result = await session.call_tool(
+ "search_messages",
+ {"query": query, "session_id": session_id, "limit": limit},
+ )
+ payload = json_result(result)
+ results = payload.get("results", [])
+ if not isinstance(results, list):
+ raise RuntimeError("search results is not a list")
+ return results
+
+
+async def delete_message(session: ClientSession, message_id: str, session_id: str) -> str:
+ result = await session.call_tool(
+ "delete_message",
+ {"message_id": message_id, "session_id": session_id},
+ )
+ payload = json_result(result)
+ status = payload.get("status", "")
+ if status != "OK":
+ raise RuntimeError(f"delete failed: {payload}")
+ return status
+
+
+async def rebuild_index(session: ClientSession) -> dict:
+ result = await session.call_tool("rebuild_index", {})
+ return json_result(result)
+
+
+async def get_system_health(session: ClientSession) -> dict:
+ result = await session.call_tool("get_system_health", {})
+ payload = json_result(result)
+ if payload.get("status") != "OK":
+ raise RuntimeError(f"health failed: {payload}")
+ return payload
+
+
+async def list_tools(session: ClientSession) -> list[dict]:
+ result = await session.list_tools()
+ return [t.model_dump() for t in result.tools]
+
+
+# ---------------------------------------------------------------------------
+# シナリオ実装
+# ---------------------------------------------------------------------------
+class ScenarioRunner:
+ def __init__(self, exe: Path) -> None:
+ self.exe = exe
+
+ def new_data_dir(self) -> Path:
+ return Path(tempfile.mkdtemp(prefix="tiny-convmem-bench-"))
+
+ def cleanup(self, data_dir: Path) -> None:
+ shutil.rmtree(data_dir, ignore_errors=True)
+
+ async def measure(
+ self,
+ name: str,
+ fn: Callable[[int], Awaitable[Any]],
+ iterations: int,
+ bytes_in: int = 0,
+ bytes_out: int = 0,
+ ) -> ScenarioResult:
+ samples: list[LatencySample] = []
+ for i in range(iterations):
+ start = time.perf_counter()
+ error: str | None = None
+ success = False
+ try:
+ await fn(i)
+ success = True
+ except Exception as exc: # noqa: BLE001
+ error = f"{type(exc).__name__}: {exc}"
+ elapsed_ms = (time.perf_counter() - start) * 1000.0
+ samples.append(
+ LatencySample(
+ operation=name,
+ duration_ms=elapsed_ms,
+ success=success,
+ error=error,
+ bytes_in=bytes_in,
+ bytes_out=bytes_out,
+ )
+ )
+ return summarize(name, samples)
+
+ async def run_write(
+ self, content: str, metadata_json: str, iterations: int, name: str
+ ) -> ScenarioResult:
+ data_dir = self.new_data_dir()
+ session_id = "bench"
+ async with ServerContext(self.exe, data_dir) as session:
+
+ async def _run(_: int) -> None:
+ await write_message(session, session_id, content, metadata_json)
+
+ try:
+ return await self.measure(
+ name,
+ _run,
+ iterations,
+ bytes_in=len(content.encode("utf-8")),
+ )
+ finally:
+ self.cleanup(data_dir)
+
+ async def run_write_small(self, iterations: int) -> ScenarioResult:
+ content = "hello durable memory"
+ return await self.run_write(
+ content,
+ make_metadata_json(),
+ iterations,
+ "write_message (small ~20B)",
+ )
+
+ async def run_write_medium(self, iterations: int) -> ScenarioResult:
+ content = random_content(DEFAULT_CONTENT_MEDIUM)
+ return await self.run_write(
+ content,
+ make_metadata_json(),
+ iterations,
+ "write_message (medium 1KiB)",
+ )
+
+ async def run_write_large(self, iterations: int) -> ScenarioResult:
+ content = random_content(DEFAULT_CONTENT_LARGE)
+ return await self.run_write(
+ content,
+ make_metadata_json(),
+ iterations,
+ "write_message (large 32KiB)",
+ )
+
+ async def run_write_with_tags(self, iterations: int) -> ScenarioResult:
+ content = "tagged message"
+ metadata_json = make_metadata_json(tags=["benchmark", "tagged"], tier="short")
+ return await self.run_write(
+ content,
+ metadata_json,
+ iterations,
+ "write_message (with tags)",
+ )
+
+ async def run_read(self, iterations: int) -> ScenarioResult:
+ data_dir = self.new_data_dir()
+ session_id = "bench"
+ async with ServerContext(self.exe, data_dir) as session:
+ # 事前に 100 件書き込み
+ for i in range(100):
+ await write_message(session, session_id, f"read target {i}")
+
+ async def _run(_: int) -> None:
+ history = await read_messages(session, session_id)
+ if len(history) != 100:
+ raise RuntimeError(f"expected 100 messages, got {len(history)}")
+
+ try:
+ return await self.measure(
+ "read_messages (100 msgs)",
+ _run,
+ iterations,
+ )
+ finally:
+ self.cleanup(data_dir)
+
+ async def run_search(self, iterations: int) -> tuple[ScenarioResult, ScenarioResult]:
+ """同じデータ・同じ query で session 絞り込み有無を比較。"""
+ data_dir = self.new_data_dir()
+ session_id = "bench"
+ other_session = "other"
+ async with ServerContext(self.exe, data_dir) as session:
+ # target session に 50 件
+ for i in range(50):
+ await write_message(
+ session,
+ session_id,
+ f"search keyword benchmark {i}",
+ make_metadata_json(tags=["target"]),
+ )
+ # 別 session に 50 件ノイズ
+ for i in range(50):
+ await write_message(
+ session,
+ other_session,
+ f"noise keyword other {i}",
+ make_metadata_json(tags=["noise"]),
+ )
+
+ async def _global(_: int) -> int:
+ results = await search_messages(session, "benchmark", "", limit=10)
+ if len(results) != 10:
+ raise RuntimeError(f"expected 10 global results, got {len(results)}")
+ return len(results)
+
+ async def _scoped(_: int) -> int:
+ results = await search_messages(session, "benchmark", session_id, limit=10)
+ if len(results) != 10:
+ raise RuntimeError(f"expected 10 scoped results, got {len(results)}")
+ # 全結果が target session であることを検証
+ for r in results:
+ if r.get("session_id") != session_id:
+ raise RuntimeError(f"scoped result leaked from {r.get('session_id')}")
+ return len(results)
+
+ try:
+ global_result = await self.measure(
+ "search_messages (global, no session)",
+ _global,
+ iterations,
+ )
+ scoped_result = await self.measure(
+ "search_messages (scoped to session)",
+ _scoped,
+ iterations,
+ )
+ return global_result, scoped_result
+ finally:
+ self.cleanup(data_dir)
+
+ async def run_delete(self, iterations: int) -> ScenarioResult:
+ data_dir = self.new_data_dir()
+ session_id = "bench"
+ async with ServerContext(self.exe, data_dir) as session:
+ ids: list[str] = []
+ for _ in range(iterations):
+ msg = await write_message(session, session_id, "to be deleted")
+ ids.append(msg["message_id"])
+
+ async def _run(i: int) -> None:
+ await delete_message(session, ids[i], session_id)
+
+ try:
+ return await self.measure("delete_message", _run, iterations)
+ finally:
+ self.cleanup(data_dir)
+
+ async def run_rebuild(self, iterations: int) -> ScenarioResult:
+ data_dir = self.new_data_dir()
+ session_id = "bench"
+ async with ServerContext(self.exe, data_dir) as session:
+ # 削除を含めた compaction 対象を 50 件作る
+ for _ in range(50):
+ msg = await write_message(session, session_id, "will compact")
+ await delete_message(session, msg["message_id"], session_id)
+ # 追加で 50 件 active
+ for i in range(50):
+ await write_message(session, session_id, f"active {i}")
+
+ async def _run(_: int) -> None:
+ payload = await rebuild_index(session)
+ if payload.get("status") != "OK":
+ raise RuntimeError(f"rebuild failed: {payload}")
+
+ try:
+ return await self.measure("rebuild_index", _run, iterations)
+ finally:
+ self.cleanup(data_dir)
+
+ async def run_health(self, iterations: int) -> ScenarioResult:
+ data_dir = self.new_data_dir()
+ session_id = "bench"
+ async with ServerContext(self.exe, data_dir) as session:
+ for _ in range(10):
+ await write_message(session, session_id, "health check")
+
+ async def _run(_: int) -> None:
+ await get_system_health(session)
+
+ try:
+ return await self.measure("get_system_health", _run, iterations)
+ finally:
+ self.cleanup(data_dir)
+
+ async def run_recovery(
+ self, iterations: int
+ ) -> tuple[ScenarioResult, ScenarioResult, ScenarioResult]:
+ data_dir = self.new_data_dir()
+ session_id = "bench"
+ # 1. データ投入 & 終了
+ async with ServerContext(self.exe, data_dir) as session:
+ for i in range(100):
+ await write_message(session, session_id, f"recovery marker {i}")
+
+ # 2. 復旧確認(メッセージ件数のみ)
+ start = time.perf_counter()
+ async with ServerContext(self.exe, data_dir) as session:
+ history = await read_messages(session, session_id)
+ if len(history) != 100:
+ raise RuntimeError(f"recovery count mismatch: {len(history)} != 100")
+ recovery_ms = (time.perf_counter() - start) * 1000.0
+ recovery_result = ScenarioResult(
+ name="recovery (restart + count check)",
+ iterations=1,
+ successes=1,
+ failures=0,
+ samples=[recovery_ms],
+ latency_min_ms=recovery_ms,
+ latency_max_ms=recovery_ms,
+ latency_mean_ms=recovery_ms,
+ latency_p50_ms=recovery_ms,
+ latency_p95_ms=recovery_ms,
+ latency_p99_ms=recovery_ms,
+ total_duration_ms=recovery_ms,
+ throughput_ops_per_sec=1000.0 / recovery_ms if recovery_ms > 0 else 0.0,
+ notes="includes process shutdown + startup + initialize + read count",
+ )
+
+ # 3. 再起動後の small read を iterations 回
+ async def _small(_: int) -> None:
+ async with ServerContext(self.exe, data_dir) as session:
+ history = await read_messages(session, session_id)
+ if len(history) != 100:
+ raise RuntimeError(f"small read count mismatch: {len(history)}")
+
+ small_result = await self.measure(
+ "recovery small read (1 message check)",
+ _small,
+ iterations,
+ )
+
+ # 4. 再起動後の full read を iterations 回
+ async def _full(_: int) -> None:
+ async with ServerContext(self.exe, data_dir) as session:
+ history = await read_messages(session, session_id)
+ if len(history) != 100:
+ raise RuntimeError(f"full read count mismatch: {len(history)}")
+
+ full_result = await self.measure(
+ "recovery full read (100 msgs)",
+ _full,
+ iterations,
+ )
+
+ self.cleanup(data_dir)
+ return recovery_result, small_result, full_result
+
+ async def run_schema_and_error_checks(self) -> dict[str, Any]:
+ data_dir = self.new_data_dir()
+ try:
+ async with ServerContext(self.exe, data_dir) as session:
+ tools = await list_tools(session)
+ tool_names = [t.get("name") for t in tools]
+ write_schema = next((t for t in tools if t.get("name") == "write_message"), {})
+ write_props = write_schema.get("inputSchema", {}).get("properties", {})
+
+ return {
+ "tool_count": len(tools),
+ "tool_names": tool_names,
+ "write_message_properties": list(write_props.keys()),
+ "write_requires": write_schema.get("inputSchema", {}).get("required", []),
+ }
+ finally:
+ self.cleanup(data_dir)
+
+ def check_jsonrpc_error_response(self) -> dict[str, Any]:
+ """subprocess で raw stdio し、JSON-RPC エラーレスポンスを検証する。"""
+ data_dir = self.new_data_dir()
+ try:
+ # 必須引数不足のリクエスト
+ request = json.dumps(
+ {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "tools/call",
+ "params": {"name": "write_message", "arguments": {}},
+ },
+ separators=(",", ":"),
+ )
+ proc = subprocess.Popen(
+ [str(self.exe), "--data-dir", str(data_dir)],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ )
+ stdout, stderr = proc.communicate(input=request + "\n", timeout=10)
+ lines = [line.strip() for line in stdout.splitlines() if line.strip()]
+ if not lines:
+ return {"error_response_present": False, "stderr": stderr[:500]}
+ try:
+ response = json.loads(lines[-1])
+ except json.JSONDecodeError as exc:
+ return {
+ "error_response_present": False,
+ "raw_last_line": lines[-1][:200],
+ "parse_error": str(exc),
+ }
+ has_jsonrpc_field = "jsonrpc" in response
+ has_error_field = "error" in response
+ return {
+ "error_response_present": has_error_field,
+ "has_jsonrpc_field": has_jsonrpc_field,
+ "has_error_field": has_error_field,
+ "error_code": response.get("error", {}).get("code") if has_error_field else None,
+ "raw_response": response,
+ }
+ except Exception as exc: # noqa: BLE001
+ return {"error_response_present": False, "exception": str(exc)}
+ finally:
+ try:
+ proc.kill()
+ except Exception: # noqa: BLE001
+ pass
+ self.cleanup(data_dir)
+
+
+# ---------------------------------------------------------------------------
+# レポート生成
+# ---------------------------------------------------------------------------
+def print_report(report: BenchmarkReport) -> None:
+ print(f"\n=== tiny-convmem-mcp 負荷テスト結果 ===")
+ print(f"server : {report.server}")
+ print(f"build_type : {report.build_type}")
+ print(f"exe_mtime : {report.exe_mtime}")
+ print(f"iterations : {report.iterations}")
+ print(f"warmup : {report.warmup}")
+ print()
+ header = (
+ f"{'scenario':<45} {'succ':>5} {'fail':>5} "
+ f"{'p50[ms]':>10} {'p95[ms]':>10} {'p99[ms]':>10} "
+ f"{'mean[ms]':>10} {'ops/s':>10}"
+ )
+ print(header)
+ print("-" * len(header))
+ for s in report.scenarios:
+ print(
+ f"{s.name:<45} {s.successes:>5} {s.failures:>5} "
+ f"{s.latency_p50_ms:>10.3f} {s.latency_p95_ms:>10.3f} {s.latency_p99_ms:>10.3f} "
+ f"{s.latency_mean_ms:>10.3f} {s.throughput_ops_per_sec:>10.2f}"
+ )
+ if report.errors:
+ print("\nerrors:")
+ for e in report.errors:
+ print(f" - {e}")
+ if report.schema_check:
+ print("\nschema / error checks:")
+ for k, v in report.schema_check.items():
+ print(f" {k}: {v}")
+ if report.health_after:
+ print("\nfinal health:")
+ for k, v in report.health_after.items():
+ print(f" {k}: {v}")
+
+
+def write_json(report: BenchmarkReport, path: Path) -> None:
+ path.write_text(json.dumps(report.as_dict(), indent=2, ensure_ascii=False), encoding="utf-8")
+
+
+def write_markdown(report: BenchmarkReport, json_path: Path, md_path: Path) -> None:
+ lines: list[str] = []
+ lines.append("# tiny-convmem-mcp 負荷テストレポート\n")
+ lines.append(f"- サーバ: `{report.server}`\n")
+ lines.append(f"- ビルド: {report.build_type}\n")
+ lines.append(f"- exe mtime: {report.exe_mtime}\n")
+ lines.append(f"- iterations: {report.iterations}\n")
+ lines.append(f"- warmup: {report.warmup}\n")
+ lines.append(f"- 計測日: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
+ lines.append(f"- raw JSON: `{json_path.name}`\n\n")
+
+ lines.append("## レイテンシサマリ\n\n")
+ lines.append(
+ "| scenario | succ | fail | p50[ms] | p95[ms] | p99[ms] | mean[ms] | ops/s |\n"
+ )
+ lines.append("|---|---|---|---|---|---|---|---|\n")
+ for s in report.scenarios:
+ lines.append(
+ f"| {s.name} | {s.successes} | {s.failures} | "
+ f"{s.latency_p50_ms:.3f} | {s.latency_p95_ms:.3f} | {s.latency_p99_ms:.3f} | "
+ f"{s.latency_mean_ms:.3f} | {s.throughput_ops_per_sec:.2f} |\n"
+ )
+ lines.append("\n")
+
+ if report.schema_check:
+ lines.append("## MCP 準拠 / エラーチェック\n\n")
+ lines.append("```json\n")
+ lines.append(json.dumps(report.schema_check, indent=2, ensure_ascii=False))
+ lines.append("\n```\n\n")
+
+ if report.health_after:
+ lines.append("## 最終 health\n\n")
+ lines.append("```json\n")
+ lines.append(json.dumps(report.health_after, indent=2, ensure_ascii=False))
+ lines.append("\n```\n\n")
+
+ if report.errors:
+ lines.append("## エラー\n\n")
+ for e in report.errors:
+ lines.append(f"- {e}\n")
+
+ md_path.write_text("".join(lines), encoding="utf-8")
+
+
+# ---------------------------------------------------------------------------
+# コマンドライン
+# ---------------------------------------------------------------------------
+def parse_args(argv: list[str]) -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="tiny-convmem-mcp ユーザ視点負荷テスト&ベンチマーク"
+ )
+ parser.add_argument(
+ "--server",
+ type=Path,
+ default=DEFAULT_RELEASE_EXE if DEFAULT_RELEASE_EXE.exists() else None,
+ help="tiny-convmem-mcp.exe のパス(デフォルト: Release ビルド)",
+ )
+ parser.add_argument(
+ "--iterations",
+ type=int,
+ default=DEFAULT_ITERATIONS,
+ help=f"各シナリオの反復回数(デフォルト {DEFAULT_ITERATIONS})",
+ )
+ parser.add_argument(
+ "--warmup",
+ type=int,
+ default=WARMUP_COUNT,
+ help=f"ウォームアップ書き込み数(デフォルト {WARMUP_COUNT})",
+ )
+ parser.add_argument(
+ "--output",
+ type=Path,
+ default=Path("benchmark_results.json"),
+ help="出力 JSON ファイル名(デフォルト: benchmark_results.json)",
+ )
+ return parser.parse_args(argv)
+
+
+async def amain(argv: list[str]) -> int:
+ args = parse_args(argv)
+
+ exe = args.server
+ if not exe or not exe.exists():
+ print(f"サーバ exe が見つかりません: {exe}")
+ return 1
+
+ exe_mtime = time.strftime(
+ "%Y-%m-%d %H:%M:%S", time.localtime(exe.stat().st_mtime)
+ )
+
+ print(f"server : {exe}")
+ print(f"mtime : {exe_mtime}")
+ print(f"iters : {args.iterations}")
+
+ runner = ScenarioRunner(exe)
+ report = BenchmarkReport(
+ server=str(exe),
+ build_type="release" if "Release" in str(exe) else "debug",
+ exe_mtime=exe_mtime,
+ iterations=args.iterations,
+ warmup=args.warmup,
+ )
+
+ # シナリオ実行
+ report.scenarios.append(await runner.run_write_small(args.iterations))
+ report.scenarios.append(await runner.run_write_medium(args.iterations))
+ report.scenarios.append(await runner.run_write_large(args.iterations))
+ report.scenarios.append(await runner.run_write_with_tags(args.iterations))
+
+ report.scenarios.append(await runner.run_read(args.iterations))
+
+ search_global, search_scoped = await runner.run_search(args.iterations)
+ report.scenarios.append(search_global)
+ report.scenarios.append(search_scoped)
+
+ report.scenarios.append(await runner.run_delete(args.iterations))
+
+ # rebuild は重いので回数を少なく
+ rebuild_iters = min(args.iterations, REBUILD_ITERATIONS)
+ report.scenarios.append(await runner.run_rebuild(rebuild_iters))
+
+ report.scenarios.append(await runner.run_health(args.iterations))
+
+ # recovery はプロセス起動/終了を伴うため回数を少なく
+ recovery_iters = min(args.iterations, REBUILD_ITERATIONS)
+ recovery_results = await runner.run_recovery(recovery_iters)
+ report.scenarios.extend(recovery_results)
+
+ # schema / error checks
+ try:
+ schema_info = await runner.run_schema_and_error_checks()
+ jsonrpc_err = runner.check_jsonrpc_error_response()
+ report.schema_check = {
+ **schema_info,
+ "jsonrpc_error_response": jsonrpc_err,
+ }
+ except Exception as exc: # noqa: BLE001
+ report.errors.append(f"schema check failed: {exc}")
+
+ # 最終 health(独立したディレクトリ)
+ try:
+ data_dir = runner.new_data_dir()
+ async with ServerContext(exe, data_dir) as session:
+ for _ in range(10):
+ await write_message(session, "final", "final check")
+ report.health_after = await get_system_health(session)
+ runner.cleanup(data_dir)
+ except Exception as exc: # noqa: BLE001
+ report.errors.append(f"final health failed: {exc}")
+
+ print_report(report)
+
+ json_path = args.output.resolve()
+ md_path = json_path.with_suffix(".md")
+ write_json(report, json_path)
+ write_markdown(report, json_path, md_path)
+ print(f"\nJSON report: {json_path}")
+ print(f"MD report : {md_path}")
+
+ return 0
+
+
+def main() -> int:
+ return asyncio.run(amain(sys.argv[1:]))
+
+
+if __name__ == "__main__":
+ sys.exit(main()) \ No newline at end of file
diff --git a/scripts/python/benchmark_results.json b/scripts/python/benchmark_results.json
new file mode 100644
index 0000000..70c5f84
--- /dev/null
+++ b/scripts/python/benchmark_results.json
@@ -0,0 +1,1221 @@
+{
+ "server": "build\\windows-vs2026\\src\\Release\\tiny-convmem-mcp.exe",
+ "build_type": "release",
+ "exe_mtime": "2026-06-30 18:49:00",
+ "iterations": 100,
+ "warmup": 5,
+ "scenarios": [
+ {
+ "name": "write_message (small ~20B)",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.377499993192032,
+ 0.37880000309087336,
+ 0.37930000689812005,
+ 0.3795999800786376,
+ 0.37960000918246806,
+ 0.3798999823629856,
+ 0.38040001527406275,
+ 0.38120002136565745,
+ 0.38120002136565745,
+ 0.38180002593435347,
+ 0.3828000044450164,
+ 0.3831000067293644,
+ 0.38339997990988195,
+ 0.38399998447857797,
+ 0.38519999361597,
+ 0.3865000035148114,
+ 0.3865000035148114,
+ 0.387699983548373,
+ 0.38800001493655145,
+ 0.38809998659417033,
+ 0.38819998735561967,
+ 0.3884999896399677,
+ 0.3887999919243157,
+ 0.3889000217895955,
+ 0.3893999964930117,
+ 0.3907000063918531,
+ 0.39090000791475177,
+ 0.39140001172199845,
+ 0.39190001552924514,
+ 0.39209998794831336,
+ 0.3921999887097627,
+ 0.39220001781359315,
+ 0.3924000193364918,
+ 0.3926999925170094,
+ 0.39280002238228917,
+ 0.39319999632425606,
+ 0.39339999784715474,
+ 0.3935999993700534,
+ 0.39370000013150275,
+ 0.39370000013150275,
+ 0.3938000008929521,
+ 0.3938000008929521,
+ 0.3938000008929521,
+ 0.3939000016544014,
+ 0.39400000241585076,
+ 0.394199974834919,
+ 0.39480000850744545,
+ 0.39480000850744545,
+ 0.3960000176448375,
+ 0.3964999923482537,
+ 0.3974999999627471,
+ 0.3974999999627471,
+ 0.3981000045314431,
+ 0.39909998304210603,
+ 0.3994000144302845,
+ 0.3994999860879034,
+ 0.4000000189989805,
+ 0.40049999370239675,
+ 0.4026000096928328,
+ 0.40309998439624906,
+ 0.40309998439624906,
+ 0.40439999429509044,
+ 0.4059000057168305,
+ 0.4059000057168305,
+ 0.4064000095240772,
+ 0.4079999926034361,
+ 0.4081999941263348,
+ 0.4093000025022775,
+ 0.4096000047866255,
+ 0.4107000131625682,
+ 0.4146999854128808,
+ 0.4169000021647662,
+ 0.41810001130215824,
+ 0.4227000172249973,
+ 0.4233999934513122,
+ 0.42699999175965786,
+ 0.4324999754317105,
+ 0.4336999845691025,
+ 0.4364999767858535,
+ 0.4375999851617962,
+ 0.440200004959479,
+ 0.45950000640004873,
+ 0.5039999959990382,
+ 0.5614999972749501,
+ 0.6695000047329813,
+ 0.6759999960195273,
+ 0.6922000029589981,
+ 0.6971000111661851,
+ 0.7052999862935394,
+ 0.751899991882965,
+ 0.7538000063505024,
+ 0.8036000072024763,
+ 0.8623999892733991,
+ 1.1503999994602054,
+ 1.2338999949861318,
+ 1.280700002098456,
+ 1.3685000012628734,
+ 1.7575999954715371,
+ 1.973899983568117,
+ 9.826800000155345
+ ],
+ "throughput_ops_per_sec": 1696.8824880031802,
+ "latency_p50_ms": 0.3969999961555004,
+ "latency_p95_ms": 1.2362399953417478,
+ "latency_p99_ms": 2.0524289837340293,
+ "latency_min_ms": 0.377499993192032,
+ "latency_max_ms": 9.826800000155345,
+ "latency_mean_ms": 0.5893159998231567,
+ "total_duration_ms": 58.931599982315674,
+ "total_bytes_in": 2000,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "write_message (medium 1KiB)",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.8056999940890819,
+ 0.8100999984890223,
+ 0.8107999747153372,
+ 0.8131999929901212,
+ 0.8137999975588173,
+ 0.8156000112649053,
+ 0.8162000158336014,
+ 0.8164000173565,
+ 0.8188999781850725,
+ 0.8194000110961497,
+ 0.819500011857599,
+ 0.8214999979827553,
+ 0.8218000002671033,
+ 0.8222999749705195,
+ 0.822600006358698,
+ 0.823500013211742,
+ 0.8236000139731914,
+ 0.8238999871537089,
+ 0.8243999909609556,
+ 0.8249999955296516,
+ 0.8258000016212463,
+ 0.8268000092357397,
+ 0.8273999847006053,
+ 0.827599986223504,
+ 0.8299000037368387,
+ 0.8302000060211867,
+ 0.8316999883390963,
+ 0.8323999936692417,
+ 0.8329999982379377,
+ 0.8358000195585191,
+ 0.835899991216138,
+ 0.8376000041607767,
+ 0.8384000102523714,
+ 0.8393999887630343,
+ 0.8409000001847744,
+ 0.8412000024691224,
+ 0.8419999794568866,
+ 0.84299998707138,
+ 0.8470999891869724,
+ 0.8492999768350273,
+ 0.8534000080544502,
+ 0.8553999941796064,
+ 0.8567000040784478,
+ 0.8581999863963574,
+ 0.8596999978180975,
+ 0.8598999993409961,
+ 0.8613999816589057,
+ 0.8622000168543309,
+ 0.8679000020492822,
+ 0.8693000127095729,
+ 0.8707999950274825,
+ 0.8709999965503812,
+ 0.8719000034034252,
+ 0.8793000015430152,
+ 0.8824000251479447,
+ 0.8836000051815063,
+ 0.8919000101741403,
+ 0.8924999856390059,
+ 0.9094999986700714,
+ 0.9098000009544194,
+ 0.9155000152532011,
+ 0.915899989195168,
+ 0.9159999899566174,
+ 0.9193999867420644,
+ 0.9273000177927315,
+ 0.9369000035803765,
+ 0.9418000117875636,
+ 0.9563000057823956,
+ 0.9693999891169369,
+ 0.9743999980855733,
+ 0.9797000093385577,
+ 0.9907999774441123,
+ 0.9986000077333301,
+ 1.0103000095114112,
+ 1.018200011458248,
+ 1.0212999768555164,
+ 1.0246000019833446,
+ 1.0355999984312803,
+ 1.039100025082007,
+ 1.0526999831199646,
+ 1.056100009009242,
+ 1.1101000127382576,
+ 1.11420001485385,
+ 1.1374999885447323,
+ 1.1451999889686704,
+ 1.1455999920144677,
+ 1.1590999783948064,
+ 1.1867000139318407,
+ 1.1878000223077834,
+ 1.2843000004068017,
+ 1.302599994232878,
+ 1.3070999993942678,
+ 1.3109999999869615,
+ 1.3165000127628446,
+ 1.5024000022094697,
+ 1.510500005679205,
+ 1.6747000045143068,
+ 2.070499991532415,
+ 3.095799998845905,
+ 7.35470000654459
+ ],
+ "throughput_ops_per_sec": 960.9231013956091,
+ "latency_p50_ms": 0.8700500038685277,
+ "latency_p95_ms": 1.5028050023829564,
+ "latency_p99_ms": 3.1383889989229137,
+ "latency_min_ms": 0.8056999940890819,
+ "latency_max_ms": 7.35470000654459,
+ "latency_mean_ms": 1.0406659997534007,
+ "total_duration_ms": 104.06659997534007,
+ "total_bytes_in": 102400,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "write_message (large 32KiB)",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 125.11520000407472,
+ 125.15000000712462,
+ 125.35799999022856,
+ 125.3632000007201,
+ 125.42379999649711,
+ 125.43210000148974,
+ 125.53900000057183,
+ 125.77370001235977,
+ 125.86850000661798,
+ 125.9147000091616,
+ 125.91969998902641,
+ 126.06720000621863,
+ 126.1858000070788,
+ 126.27499998779967,
+ 126.4798000047449,
+ 126.50889999349602,
+ 126.54379999730736,
+ 126.61420000949875,
+ 126.64339999901131,
+ 126.7128000035882,
+ 126.7682000179775,
+ 126.86439999379218,
+ 126.87880001612939,
+ 126.8860999844037,
+ 126.91540000378154,
+ 126.93329999456182,
+ 127.01460000243969,
+ 127.02749998425134,
+ 127.05469998763874,
+ 127.07289998070337,
+ 127.24030000390485,
+ 127.27080000331625,
+ 127.27389999781735,
+ 127.32390000019222,
+ 127.35260001500137,
+ 127.4136999854818,
+ 127.45729999733157,
+ 127.45970001560636,
+ 127.47339997440577,
+ 127.54390001646243,
+ 127.78029998298734,
+ 127.81939998967573,
+ 127.96220000018366,
+ 128.00500000594184,
+ 128.0274000018835,
+ 128.07460001204163,
+ 128.12179999309592,
+ 128.2068000000436,
+ 128.22879999293946,
+ 128.23929998558015,
+ 128.24489999911748,
+ 128.34130000555888,
+ 128.36620002053678,
+ 128.3730999857653,
+ 128.49940001615323,
+ 128.55559997842647,
+ 128.56750001083128,
+ 128.64119998994283,
+ 128.70810000458732,
+ 128.88639999437146,
+ 128.93649999750778,
+ 128.96880001062527,
+ 129.1536999924574,
+ 129.15849999990314,
+ 129.2675999866333,
+ 129.30149998283014,
+ 129.32770000770688,
+ 129.37650000094436,
+ 129.3846000044141,
+ 129.65940000140108,
+ 129.78940000175498,
+ 129.8231999971904,
+ 129.8806999984663,
+ 129.9128000100609,
+ 129.92979999398813,
+ 130.0004000077024,
+ 130.02430001506582,
+ 130.08550001541153,
+ 130.09509997209534,
+ 130.15910002286546,
+ 130.23970002541319,
+ 130.28650000342168,
+ 130.31850001425482,
+ 130.6365999917034,
+ 130.67969999974594,
+ 131.08530000317842,
+ 131.48440001532435,
+ 131.66660000570118,
+ 132.17470000381581,
+ 132.41049999487586,
+ 132.53900001291186,
+ 132.6581999892369,
+ 132.873099995777,
+ 132.99919999553822,
+ 133.32520000403747,
+ 133.37250001495704,
+ 133.39540001470596,
+ 133.44799997867085,
+ 133.55840000440367,
+ 133.7197999819182
+ ],
+ "throughput_ops_per_sec": 7.774314812877668,
+ "latency_p50_ms": 128.2420999923488,
+ "latency_p95_ms": 133.32756500458345,
+ "latency_p99_ms": 133.5600140041788,
+ "latency_min_ms": 125.11520000407472,
+ "latency_max_ms": 133.7197999819182,
+ "latency_mean_ms": 128.62869900040096,
+ "total_duration_ms": 12862.869900040096,
+ "total_bytes_in": 3276800,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "write_message (with tags)",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.3721999819390476,
+ 0.3733999910764396,
+ 0.3758000093512237,
+ 0.3761000116355717,
+ 0.37650001468136907,
+ 0.37659998633898795,
+ 0.3767999878618866,
+ 0.37719999090768397,
+ 0.37779999547638,
+ 0.3803999861702323,
+ 0.3816999960690737,
+ 0.381799996830523,
+ 0.3831999783869833,
+ 0.38400001358240843,
+ 0.3843999875243753,
+ 0.3852000227198005,
+ 0.38579999818466604,
+ 0.3859999997075647,
+ 0.3859999997075647,
+ 0.38610000046901405,
+ 0.38660000427626073,
+ 0.3868000057991594,
+ 0.3870000073220581,
+ 0.3871000080835074,
+ 0.38740001036785543,
+ 0.38750001112930477,
+ 0.38759998278692365,
+ 0.38759998278692365,
+ 0.388599990401417,
+ 0.38869999116286635,
+ 0.38869999116286635,
+ 0.3887999919243157,
+ 0.3887999919243157,
+ 0.38889999268576503,
+ 0.38899999344721437,
+ 0.38910002331249416,
+ 0.38910002331249416,
+ 0.3892999957315624,
+ 0.38930002483539283,
+ 0.38949999725446105,
+ 0.38979999953880906,
+ 0.39000000106170774,
+ 0.39000000106170774,
+ 0.3902000025846064,
+ 0.3902000025846064,
+ 0.39030000334605575,
+ 0.39030000334605575,
+ 0.3904000041075051,
+ 0.39060000563040376,
+ 0.39110000943765044,
+ 0.3913000109605491,
+ 0.391699984902516,
+ 0.39220001781359315,
+ 0.39259999175556004,
+ 0.3929999948013574,
+ 0.39339999784715474,
+ 0.39339999784715474,
+ 0.3934999986086041,
+ 0.39370000013150275,
+ 0.3939000016544014,
+ 0.3944000054616481,
+ 0.39450000622309744,
+ 0.39480000850744545,
+ 0.3952000115532428,
+ 0.39579998701810837,
+ 0.39629999082535505,
+ 0.39709999691694975,
+ 0.3971999976783991,
+ 0.3999000182375312,
+ 0.4002000205218792,
+ 0.40049999370239675,
+ 0.4026000096928328,
+ 0.40329998591914773,
+ 0.40339998668059707,
+ 0.40400002035312355,
+ 0.4047999973408878,
+ 0.40550000267103314,
+ 0.4056000034324825,
+ 0.40610000723972917,
+ 0.4157000221312046,
+ 0.4163999983575195,
+ 0.4254999803379178,
+ 0.42699999175965786,
+ 0.44070000876672566,
+ 0.4676000098697841,
+ 0.48760001664049923,
+ 0.5067000165581703,
+ 0.5569999921135604,
+ 0.6055999838281423,
+ 0.609799986705184,
+ 0.6585999799426645,
+ 0.6604999944102019,
+ 0.6605999951716512,
+ 0.6743999838363379,
+ 0.6996000010985881,
+ 0.7108999998308718,
+ 0.7702000148128718,
+ 0.7889999833423644,
+ 1.1153999948874116,
+ 1.4324000221677125
+ ],
+ "throughput_ops_per_sec": 2255.4830781503824,
+ "latency_p50_ms": 0.3912000101990998,
+ "latency_p95_ms": 0.7001650010352023,
+ "latency_p99_ms": 1.1185699951602162,
+ "latency_min_ms": 0.3721999819390476,
+ "latency_max_ms": 1.4324000221677125,
+ "latency_mean_ms": 0.44336400023894385,
+ "total_duration_ms": 44.336400023894385,
+ "total_bytes_in": 1400,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "read_messages (100 msgs)",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.31820000731386244,
+ 0.31939998734742403,
+ 0.31999999191612005,
+ 0.3208999987691641,
+ 0.3212000010535121,
+ 0.321599974995479,
+ 0.32240001019090414,
+ 0.3234000178053975,
+ 0.3234999894630164,
+ 0.32359999022446573,
+ 0.32589997863397,
+ 0.3259000077378005,
+ 0.32619998091831803,
+ 0.3271000168751925,
+ 0.3274999908171594,
+ 0.3276999923400581,
+ 0.32770002144388855,
+ 0.32820002525113523,
+ 0.32839999767020345,
+ 0.3285999991931021,
+ 0.3291000030003488,
+ 0.3294000052846968,
+ 0.3294000052846968,
+ 0.32970000756904483,
+ 0.32980000833049417,
+ 0.3299000090919435,
+ 0.3302999830339104,
+ 0.3305999853182584,
+ 0.33069998607970774,
+ 0.33080001594498754,
+ 0.33080001594498754,
+ 0.33099998836405575,
+ 0.33170002279803157,
+ 0.33290000283159316,
+ 0.33370000892318785,
+ 0.33370000892318785,
+ 0.3341000119689852,
+ 0.3341000119689852,
+ 0.33489998895674944,
+ 0.3352999920025468,
+ 0.33549999352544546,
+ 0.33569999504834414,
+ 0.3364000003784895,
+ 0.33650000113993883,
+ 0.3378999826963991,
+ 0.33820001408457756,
+ 0.33840001560747623,
+ 0.3384999872650951,
+ 0.3387999895494431,
+ 0.3391999925952405,
+ 0.33939999411813915,
+ 0.3395999956410378,
+ 0.3400999994482845,
+ 0.34080000477842987,
+ 0.34089997643604875,
+ 0.3412999794818461,
+ 0.3415999817661941,
+ 0.3422000154387206,
+ 0.34229998709633946,
+ 0.34240001696161926,
+ 0.3431999939493835,
+ 0.34329999471083283,
+ 0.34329999471083283,
+ 0.34389999927952886,
+ 0.3440000000409782,
+ 0.3451000084169209,
+ 0.3452999808359891,
+ 0.3457000129856169,
+ 0.34630001755431294,
+ 0.34680002136155963,
+ 0.3468999930191785,
+ 0.3476999991107732,
+ 0.34840000444091856,
+ 0.34890000824816525,
+ 0.34920001053251326,
+ 0.3512000257615,
+ 0.35650000791065395,
+ 0.3569999826140702,
+ 0.3603999793995172,
+ 0.361700018402189,
+ 0.3696999920066446,
+ 0.3711000026669353,
+ 0.37269998574629426,
+ 0.3735999925993383,
+ 0.38109999150037766,
+ 0.3845000173896551,
+ 0.39420000393874943,
+ 0.4026000096928328,
+ 0.4251000063959509,
+ 0.4325999761931598,
+ 0.4352999967522919,
+ 0.544899987289682,
+ 0.682699988828972,
+ 0.739200011594221,
+ 0.7657000096514821,
+ 0.8477999945171177,
+ 0.9815999947022647,
+ 1.0258000111207366,
+ 1.3758999994024634,
+ 2.2730999917257577
+ ],
+ "throughput_ops_per_sec": 2471.63795559416,
+ "latency_p50_ms": 0.3392999933566898,
+ "latency_p95_ms": 0.7698050088947637,
+ "latency_p99_ms": 1.384871999325701,
+ "latency_min_ms": 0.31820000731386244,
+ "latency_max_ms": 2.2730999917257577,
+ "latency_mean_ms": 0.40458999981638044,
+ "total_duration_ms": 40.458999981638044,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "search_messages (global, no session)",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.14349998673424125,
+ 0.14409999130293727,
+ 0.14450002345256507,
+ 0.1446000242140144,
+ 0.14489999739453197,
+ 0.14509999891743064,
+ 0.14540000120177865,
+ 0.145500001963228,
+ 0.145800004247576,
+ 0.14590000500902534,
+ 0.14590000500902534,
+ 0.14600000577047467,
+ 0.146100006531924,
+ 0.1461999781895429,
+ 0.14629997895099223,
+ 0.14630000805482268,
+ 0.14640000881627202,
+ 0.14640000881627202,
+ 0.14640000881627202,
+ 0.14669998199678957,
+ 0.14670001110062003,
+ 0.14670001110062003,
+ 0.1467999827582389,
+ 0.14680001186206937,
+ 0.14689998351968825,
+ 0.14709998504258692,
+ 0.1474000164307654,
+ 0.14750001719221473,
+ 0.1475999888498336,
+ 0.14779999037273228,
+ 0.14779999037273228,
+ 0.14799999189563096,
+ 0.14799999189563096,
+ 0.1480999926570803,
+ 0.14829999417997897,
+ 0.1483999949414283,
+ 0.1483999949414283,
+ 0.14840002404525876,
+ 0.14860002556815743,
+ 0.14869999722577631,
+ 0.14890002785250545,
+ 0.14899999951012433,
+ 0.14899999951012433,
+ 0.149200001033023,
+ 0.14960000407882035,
+ 0.14980000560171902,
+ 0.15020000864751637,
+ 0.15020000864751637,
+ 0.15020000864751637,
+ 0.15029998030513525,
+ 0.1503999810665846,
+ 0.15050001093186438,
+ 0.15079998411238194,
+ 0.15100001473911107,
+ 0.15140001778490841,
+ 0.15169999096542597,
+ 0.15199999324977398,
+ 0.15239999629557133,
+ 0.15239999629557133,
+ 0.15249999705702066,
+ 0.15259999781847,
+ 0.15259999781847,
+ 0.152900000102818,
+ 0.15300000086426735,
+ 0.1531000016257167,
+ 0.1531000016257167,
+ 0.15380000695586205,
+ 0.15380000695586205,
+ 0.15399997937493026,
+ 0.15399997937493026,
+ 0.15480001457035542,
+ 0.1548999862279743,
+ 0.15539999003522098,
+ 0.15559999155811965,
+ 0.15720000374130905,
+ 0.15740000526420772,
+ 0.1579000090714544,
+ 0.15819998225197196,
+ 0.16039999900385737,
+ 0.16160000814124942,
+ 0.1640999980736524,
+ 0.16450000111944973,
+ 0.1650999765843153,
+ 0.16529997810721397,
+ 0.16590001177974045,
+ 0.16640001558698714,
+ 0.16709999181330204,
+ 0.16709999181330204,
+ 0.16769999638199806,
+ 0.16820000018924475,
+ 0.17189999925903976,
+ 0.1742000167723745,
+ 0.17829998978413641,
+ 0.18599999020807445,
+ 0.18870001076720655,
+ 0.19990000873804092,
+ 0.22330001229420304,
+ 0.2859000233002007,
+ 0.7118000066839159,
+ 1.6556000045966357
+ ],
+ "throughput_ops_per_sec": 5675.11122125873,
+ "latency_p50_ms": 0.15034998068585992,
+ "latency_p95_ms": 0.18926001066574824,
+ "latency_p99_ms": 0.7212380066630478,
+ "latency_min_ms": 0.14349998673424125,
+ "latency_max_ms": 1.6556000045966357,
+ "latency_mean_ms": 0.176208000339102,
+ "total_duration_ms": 17.6208000339102,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "search_messages (scoped to session)",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.1453000004403293,
+ 0.14540000120177865,
+ 0.14590000500902534,
+ 0.146100006531924,
+ 0.1467999827582389,
+ 0.14680001186206937,
+ 0.14689998351968825,
+ 0.1469000126235187,
+ 0.14710001414641738,
+ 0.14720001490786672,
+ 0.1472999865654856,
+ 0.1472999865654856,
+ 0.1474000164307654,
+ 0.1474000164307654,
+ 0.14749998808838427,
+ 0.14749998808838427,
+ 0.1475999888498336,
+ 0.14769998961128294,
+ 0.14769998961128294,
+ 0.14779999037273228,
+ 0.14789999113418162,
+ 0.1480999926570803,
+ 0.14810002176091075,
+ 0.14819999341852963,
+ 0.1483999949414283,
+ 0.1483999949414283,
+ 0.14840002404525876,
+ 0.14860002556815743,
+ 0.14869999722577631,
+ 0.14869999722577631,
+ 0.148899998748675,
+ 0.14930000179447234,
+ 0.14930000179447234,
+ 0.14930000179447234,
+ 0.14940000255592167,
+ 0.14960000407882035,
+ 0.14990000636316836,
+ 0.1500000071246177,
+ 0.15010000788606703,
+ 0.15010000788606703,
+ 0.1503000094089657,
+ 0.15050001093186438,
+ 0.15050001093186438,
+ 0.15059998258948326,
+ 0.1506999833509326,
+ 0.15079998411238194,
+ 0.15079998411238194,
+ 0.1509999856352806,
+ 0.15150001854635775,
+ 0.15159999020397663,
+ 0.15159999020397663,
+ 0.1516000193078071,
+ 0.15209999401122332,
+ 0.15269999857991934,
+ 0.152900000102818,
+ 0.15300000086426735,
+ 0.15300000086426735,
+ 0.15300000086426735,
+ 0.15350000467151403,
+ 0.15360000543296337,
+ 0.1537999778520316,
+ 0.15399997937493026,
+ 0.15410000924021006,
+ 0.15429998165927827,
+ 0.15440001152455807,
+ 0.1545000122860074,
+ 0.15460001304745674,
+ 0.15469998470507562,
+ 0.15509998775087297,
+ 0.15550001990050077,
+ 0.15550001990050077,
+ 0.15559999155811965,
+ 0.15589999384246767,
+ 0.156299996888265,
+ 0.15669999993406236,
+ 0.15750000602565706,
+ 0.15839998377487063,
+ 0.15880001592449844,
+ 0.15899998834356666,
+ 0.16170000890269876,
+ 0.16309999045915902,
+ 0.16540000797249377,
+ 0.166199984960258,
+ 0.16960001084953547,
+ 0.18049997743219137,
+ 0.18440000712871552,
+ 0.19439999596215785,
+ 0.2193999825976789,
+ 0.21989998640492558,
+ 0.2463000128045678,
+ 0.25069998810067773,
+ 0.2605999761726707,
+ 0.2620999875944108,
+ 0.27439999394118786,
+ 0.4681999853346497,
+ 0.5210000090301037,
+ 0.6234000029508024,
+ 0.7620000105816871,
+ 0.8063999994192272,
+ 0.9846999892033637
+ ],
+ "throughput_ops_per_sec": 5209.038750416192,
+ "latency_p50_ms": 0.15159999020397663,
+ "latency_p95_ms": 0.4708399865194222,
+ "latency_p99_ms": 0.8081829993170695,
+ "latency_min_ms": 0.1453000004403293,
+ "latency_max_ms": 0.9846999892033637,
+ "latency_mean_ms": 0.19197399902623147,
+ "total_duration_ms": 19.197399902623147,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "delete_message",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.357700017048046,
+ 0.3607999824453145,
+ 0.361100013833493,
+ 0.3622999938670546,
+ 0.3623999946285039,
+ 0.36269999691285193,
+ 0.3628999984357506,
+ 0.36290002753958106,
+ 0.3630999999586493,
+ 0.3637000045273453,
+ 0.3640000068116933,
+ 0.36429997999221087,
+ 0.3643000090960413,
+ 0.364800012903288,
+ 0.36559998989105225,
+ 0.3657999914139509,
+ 0.36600002204068005,
+ 0.36669999826699495,
+ 0.3729000163730234,
+ 0.3732000186573714,
+ 0.37349999183788896,
+ 0.37429999792948365,
+ 0.3744999994523823,
+ 0.374700000975281,
+ 0.3752000047825277,
+ 0.3755999787244946,
+ 0.37609998253174126,
+ 0.3761000116355717,
+ 0.37629998405463994,
+ 0.3775999939534813,
+ 0.37819999852217734,
+ 0.37880000309087336,
+ 0.3794000076595694,
+ 0.3795000084210187,
+ 0.3795000084210187,
+ 0.3809999907389283,
+ 0.3816999960690737,
+ 0.38320000749081373,
+ 0.3836000105366111,
+ 0.3837000112980604,
+ 0.3837000112980604,
+ 0.38389998371712863,
+ 0.38469998980872333,
+ 0.3848000196740031,
+ 0.38640000275336206,
+ 0.3871000080835074,
+ 0.38740001036785543,
+ 0.38759998278692365,
+ 0.38949999725446105,
+ 0.3896999987773597,
+ 0.39010000182315707,
+ 0.3957000153604895,
+ 0.3966999938711524,
+ 0.39820000529289246,
+ 0.4026000096928328,
+ 0.40290001197718084,
+ 0.40350001654587686,
+ 0.4078000201843679,
+ 0.41259999852627516,
+ 0.41429998236708343,
+ 0.41719997534528375,
+ 0.42189998202957213,
+ 0.4236000240780413,
+ 0.4243000003043562,
+ 0.42570001096464694,
+ 0.4329000075813383,
+ 0.4335000121500343,
+ 0.4374999844003469,
+ 0.4382000188343227,
+ 0.45029999455437064,
+ 0.46370000927709043,
+ 0.4668000037781894,
+ 0.47259998973459005,
+ 0.4845999937970191,
+ 0.49330000183545053,
+ 0.4965999978594482,
+ 0.5030999891459942,
+ 0.5033000197727233,
+ 0.5043999990448356,
+ 0.5099999834783375,
+ 0.5201000021770597,
+ 0.5224999913480133,
+ 0.5225000204518437,
+ 0.523299997439608,
+ 0.5238000012468547,
+ 0.529799988726154,
+ 0.5446999857667834,
+ 0.5487999878823757,
+ 0.5502999993041158,
+ 0.5794999888166785,
+ 0.585700006922707,
+ 0.5951999919489026,
+ 0.6125000072643161,
+ 0.614999997196719,
+ 0.7118000066839159,
+ 0.7120000082068145,
+ 0.7795999990776181,
+ 0.8068000024650246,
+ 0.8276000153273344,
+ 0.918099976843223
+ ],
+ "throughput_ops_per_sec": 2248.605859510455,
+ "latency_p50_ms": 0.3899000003002584,
+ "latency_p95_ms": 0.7118100067600608,
+ "latency_p99_ms": 0.8285050149424937,
+ "latency_min_ms": 0.357700017048046,
+ "latency_max_ms": 0.918099976843223,
+ "latency_mean_ms": 0.4447200009599328,
+ "total_duration_ms": 44.47200009599328,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "rebuild_index",
+ "iterations": 5,
+ "successes": 5,
+ "failures": 0,
+ "samples": [
+ 5.564399994909763,
+ 5.966299999272451,
+ 6.8909999972675,
+ 7.271500013303012,
+ 7.908600004157051
+ ],
+ "throughput_ops_per_sec": 148.8015522583377,
+ "latency_p50_ms": 6.8909999972675,
+ "latency_p95_ms": 7.7811800059862435,
+ "latency_p99_ms": 7.88311600452289,
+ "latency_min_ms": 5.564399994909763,
+ "latency_max_ms": 7.908600004157051,
+ "latency_mean_ms": 6.720360001781955,
+ "total_duration_ms": 33.60180000890978,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "get_system_health",
+ "iterations": 100,
+ "successes": 100,
+ "failures": 0,
+ "samples": [
+ 0.09960000170394778,
+ 0.09960000170394778,
+ 0.09980000322684646,
+ 0.0999000039882958,
+ 0.10070001007989049,
+ 0.10070001007989049,
+ 0.10080001084133983,
+ 0.10080001084133983,
+ 0.10090001160278916,
+ 0.10119998478330672,
+ 0.10129998554475605,
+ 0.10129998554475605,
+ 0.10139998630620539,
+ 0.10149998706765473,
+ 0.10160001693293452,
+ 0.10179998935200274,
+ 0.10199999087490141,
+ 0.10200001997873187,
+ 0.10219999239780009,
+ 0.10239999392069876,
+ 0.1024999946821481,
+ 0.10269999620504677,
+ 0.10269999620504677,
+ 0.10289999772794545,
+ 0.10309999925084412,
+ 0.10309999925084412,
+ 0.10350000229664147,
+ 0.1039999770000577,
+ 0.10449998080730438,
+ 0.10450000991113484,
+ 0.10470001143403351,
+ 0.10499998461455107,
+ 0.1050999853760004,
+ 0.10540001676417887,
+ 0.10550001752562821,
+ 0.10560001828707755,
+ 0.1058999914675951,
+ 0.1060000213328749,
+ 0.10609999299049377,
+ 0.10609999299049377,
+ 0.10620002285577357,
+ 0.10679999832063913,
+ 0.10710000060498714,
+ 0.10740000288933516,
+ 0.10750000365078449,
+ 0.10830000974237919,
+ 0.10869998368434608,
+ 0.10939998901449144,
+ 0.10989999282173812,
+ 0.10989999282173812,
+ 0.1100999943446368,
+ 0.11130000348202884,
+ 0.11160000576637685,
+ 0.11210000957362354,
+ 0.11249998351559043,
+ 0.11270001414231956,
+ 0.1128000149037689,
+ 0.11309998808428645,
+ 0.11350002023391426,
+ 0.11419999646022916,
+ 0.11480000102892518,
+ 0.11530000483617187,
+ 0.11589998030103743,
+ 0.11610001092776656,
+ 0.11610001092776656,
+ 0.11679998715408146,
+ 0.1168999879155308,
+ 0.11749999248422682,
+ 0.11799999629147351,
+ 0.11860000086016953,
+ 0.11910000466741621,
+ 0.1196000084746629,
+ 0.12079998850822449,
+ 0.1210999907925725,
+ 0.12110001989640296,
+ 0.12119999155402184,
+ 0.12129999231547117,
+ 0.12139999307692051,
+ 0.12250000145286322,
+ 0.12389998300932348,
+ 0.12519999290816486,
+ 0.12569999671541154,
+ 0.12620000052265823,
+ 0.1264000020455569,
+ 0.12889999197795987,
+ 0.12919999426230788,
+ 0.13169998419471085,
+ 0.1328999933321029,
+ 0.1340000017080456,
+ 0.14310001279227436,
+ 0.15010000788606703,
+ 0.15830001211725175,
+ 0.16319999122060835,
+ 0.16820000018924475,
+ 0.17540002590976655,
+ 0.21830000332556665,
+ 0.2316000172868371,
+ 0.25749998167157173,
+ 0.7612000044900924,
+ 0.8997000113595277
+ ],
+ "throughput_ops_per_sec": 7587.0807292005875,
+ "latency_p50_ms": 0.10999999358318746,
+ "latency_p95_ms": 0.17754502478055645,
+ "latency_p99_ms": 0.7625850045587874,
+ "latency_min_ms": 0.09960000170394778,
+ "latency_max_ms": 0.8997000113595277,
+ "latency_mean_ms": 0.1318029998219572,
+ "total_duration_ms": 13.18029998219572,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "recovery (restart + count check)",
+ "iterations": 1,
+ "successes": 1,
+ "failures": 0,
+ "samples": [
+ 20.377600012579933
+ ],
+ "throughput_ops_per_sec": 49.073492432016465,
+ "latency_p50_ms": 20.377600012579933,
+ "latency_p95_ms": 20.377600012579933,
+ "latency_p99_ms": 20.377600012579933,
+ "latency_min_ms": 20.377600012579933,
+ "latency_max_ms": 20.377600012579933,
+ "latency_mean_ms": 20.377600012579933,
+ "total_duration_ms": 20.377600012579933,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": "includes process shutdown + startup + initialize + read count"
+ },
+ {
+ "name": "recovery small read (1 message check)",
+ "iterations": 5,
+ "successes": 5,
+ "failures": 0,
+ "samples": [
+ 16.233500005910173,
+ 16.51690001017414,
+ 16.59829998970963,
+ 16.701499989721924,
+ 16.76729999599047
+ ],
+ "throughput_ops_per_sec": 60.373713291427464,
+ "latency_p50_ms": 16.59829998970963,
+ "latency_p95_ms": 16.75413999473676,
+ "latency_p99_ms": 16.764667995739728,
+ "latency_min_ms": 16.233500005910173,
+ "latency_max_ms": 16.76729999599047,
+ "latency_mean_ms": 16.563499998301268,
+ "total_duration_ms": 82.81749999150634,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ },
+ {
+ "name": "recovery full read (100 msgs)",
+ "iterations": 5,
+ "successes": 5,
+ "failures": 0,
+ "samples": [
+ 15.85620001424104,
+ 15.893400006461889,
+ 16.1155000096187,
+ 16.868900012923405,
+ 18.851200002245605
+ ],
+ "throughput_ops_per_sec": 59.81920240998151,
+ "latency_p50_ms": 16.1155000096187,
+ "latency_p95_ms": 18.454740004381165,
+ "latency_p99_ms": 18.771908002672717,
+ "latency_min_ms": 15.85620001424104,
+ "latency_max_ms": 18.851200002245605,
+ "latency_mean_ms": 16.717040009098127,
+ "total_duration_ms": 83.58520004549064,
+ "total_bytes_in": 0,
+ "total_bytes_out": 0,
+ "notes": ""
+ }
+ ],
+ "health_after": {
+ "status": "OK",
+ "format_version": 1,
+ "log_size": 920,
+ "snapshot_size": 0,
+ "active_messages": 10,
+ "deleted_messages": 0,
+ "last_sequence": 10,
+ "recovered_tail": false,
+ "snapshot_valid": false,
+ "last_error": ""
+ },
+ "schema_check": {
+ "tool_count": 6,
+ "tool_names": [
+ "write_message",
+ "read_messages",
+ "search_messages",
+ "delete_message",
+ "rebuild_index",
+ "get_system_health"
+ ],
+ "write_message_properties": [
+ "session_id",
+ "role",
+ "content",
+ "metadata_json"
+ ],
+ "write_requires": [
+ "session_id",
+ "role",
+ "content"
+ ],
+ "jsonrpc_error_response": {
+ "error_response_present": true,
+ "has_jsonrpc_field": true,
+ "has_error_field": true,
+ "error_code": -32601,
+ "raw_response": {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "error": {
+ "code": -32601,
+ "message": "client not initialized"
+ }
+ }
+ }
+ },
+ "errors": []
+} \ No newline at end of file
diff --git a/scripts/python/benchmark_results.md b/scripts/python/benchmark_results.md
new file mode 100644
index 0000000..9d512a3
--- /dev/null
+++ b/scripts/python/benchmark_results.md
@@ -0,0 +1,85 @@
+# tiny-convmem-mcp 負荷テストレポート
+- サーバ: `build\windows-vs2026\src\Release\tiny-convmem-mcp.exe`
+- ビルド: release
+- exe mtime: 2026-06-30 18:49:00
+- iterations: 100
+- warmup: 5
+- 計測日: 2026-06-30 18:49:14
+- raw JSON: `benchmark_results.json`
+
+## レイテンシサマリ
+
+| scenario | succ | fail | p50[ms] | p95[ms] | p99[ms] | mean[ms] | ops/s |
+|---|---|---|---|---|---|---|---|
+| write_message (small ~20B) | 100 | 0 | 0.397 | 1.236 | 2.052 | 0.589 | 1696.88 |
+| write_message (medium 1KiB) | 100 | 0 | 0.870 | 1.503 | 3.138 | 1.041 | 960.92 |
+| write_message (large 32KiB) | 100 | 0 | 128.242 | 133.328 | 133.560 | 128.629 | 7.77 |
+| write_message (with tags) | 100 | 0 | 0.391 | 0.700 | 1.119 | 0.443 | 2255.48 |
+| read_messages (100 msgs) | 100 | 0 | 0.339 | 0.770 | 1.385 | 0.405 | 2471.64 |
+| search_messages (global, no session) | 100 | 0 | 0.150 | 0.189 | 0.721 | 0.176 | 5675.11 |
+| search_messages (scoped to session) | 100 | 0 | 0.152 | 0.471 | 0.808 | 0.192 | 5209.04 |
+| delete_message | 100 | 0 | 0.390 | 0.712 | 0.829 | 0.445 | 2248.61 |
+| rebuild_index | 5 | 0 | 6.891 | 7.781 | 7.883 | 6.720 | 148.80 |
+| get_system_health | 100 | 0 | 0.110 | 0.178 | 0.763 | 0.132 | 7587.08 |
+| recovery (restart + count check) | 1 | 0 | 20.378 | 20.378 | 20.378 | 20.378 | 49.07 |
+| recovery small read (1 message check) | 5 | 0 | 16.598 | 16.754 | 16.765 | 16.563 | 60.37 |
+| recovery full read (100 msgs) | 5 | 0 | 16.116 | 18.455 | 18.772 | 16.717 | 59.82 |
+
+## MCP 準拠 / エラーチェック
+
+```json
+{
+ "tool_count": 6,
+ "tool_names": [
+ "write_message",
+ "read_messages",
+ "search_messages",
+ "delete_message",
+ "rebuild_index",
+ "get_system_health"
+ ],
+ "write_message_properties": [
+ "session_id",
+ "role",
+ "content",
+ "metadata_json"
+ ],
+ "write_requires": [
+ "session_id",
+ "role",
+ "content"
+ ],
+ "jsonrpc_error_response": {
+ "error_response_present": true,
+ "has_jsonrpc_field": true,
+ "has_error_field": true,
+ "error_code": -32601,
+ "raw_response": {
+ "jsonrpc": "2.0",
+ "id": 2,
+ "error": {
+ "code": -32601,
+ "message": "client not initialized"
+ }
+ }
+ }
+}
+```
+
+## 最終 health
+
+```json
+{
+ "status": "OK",
+ "format_version": 1,
+ "log_size": 920,
+ "snapshot_size": 0,
+ "active_messages": 10,
+ "deleted_messages": 0,
+ "last_sequence": 10,
+ "recovered_tail": false,
+ "snapshot_valid": false,
+ "last_error": ""
+}
+```
+
diff --git a/scripts/python/p0_regression.py b/scripts/python/p0_regression.py
new file mode 100644
index 0000000..61cd426
--- /dev/null
+++ b/scripts/python/p0_regression.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+"""P0 MCP SDK interoperability and long-running connection regression test."""
+
+from __future__ import annotations
+
+import argparse
+import asyncio
+import json
+import os
+import shutil
+import tempfile
+from pathlib import Path
+
+from mcp.client.session import ClientSession
+from mcp.client.stdio import StdioServerParameters, stdio_client
+from mcp.types import CallToolResult
+
+
+def payload(result: CallToolResult) -> dict:
+ if result.isError:
+ raise AssertionError(f"tool error: {result.content!r}")
+ texts = [item.text for item in result.content if item.type == "text"]
+ return json.loads("".join(texts))
+
+
+async def run(server: Path, iterations: int) -> None:
+ data_dir = Path(tempfile.mkdtemp(prefix="tiny-convmem-sdk-p0-"))
+ params = StdioServerParameters(
+ command=str(server.resolve()),
+ args=["--data-dir", str(data_dir)],
+ env=dict(os.environ),
+ )
+ completed = 0
+ with tempfile.TemporaryFile(mode="w+", encoding="utf-8") as errlog:
+ try:
+ async with stdio_client(params, errlog=errlog) as (read_stream, write_stream):
+ async with ClientSession(read_stream, write_stream) as session:
+ await session.initialize()
+ listed = await session.list_tools()
+ assert {tool.name for tool in listed.tools} == {
+ "write_message",
+ "read_messages",
+ "search_messages",
+ "delete_message",
+ "rebuild_index",
+ "get_system_health",
+ }
+ for index in range(20):
+ payload(
+ await session.call_tool(
+ "write_message",
+ {
+ "session_id": "target",
+ "role": "user",
+ "content": f"sdk needle target {index}",
+ },
+ )
+ )
+ for index in range(10):
+ payload(
+ await session.call_tool(
+ "write_message",
+ {
+ "session_id": "other",
+ "role": "user",
+ "content": f"sdk needle other {index}",
+ },
+ )
+ )
+
+ for index in range(iterations):
+ arguments = {"query": "needle"}
+ if index % 2:
+ arguments["session_id"] = "target"
+ results = payload(
+ await session.call_tool("search_messages", arguments)
+ )["results"]
+ assert len(results) == 20
+ if index % 2:
+ assert all(item["session_id"] == "target" for item in results)
+ completed += 1
+ errlog.flush()
+ errlog.seek(0)
+ server_stderr = errlog.read()
+ if "AddressSanitizer" in server_stderr or "ERROR:" in server_stderr:
+ raise AssertionError(f"server diagnostics reported a failure:\n{server_stderr}")
+ except Exception as exc:
+ errlog.flush()
+ errlog.seek(0)
+ raise RuntimeError(
+ f"failed after {completed}/{iterations} calls; server stderr:\n{errlog.read()}"
+ ) from exc
+ finally:
+ shutil.rmtree(data_dir, ignore_errors=True)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--server", type=Path, required=True)
+ parser.add_argument("--iterations", type=int, default=1000)
+ args = parser.parse_args()
+ asyncio.run(run(args.server, args.iterations))
+ print(f"Python MCP SDK completed {args.iterations} alternating searches")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/mcp/server.c b/src/mcp/server.c
index a0ff436..498e9f4 100644
--- a/src/mcp/server.c
+++ b/src/mcp/server.c
@@ -30,6 +30,10 @@ tiny_convmem_server_run(FILE *input, FILE *output, tiny_store_t *store)
return 1;
}
status = mcpc_server_start(server);
+ if (status != MCPC_EC_0)
+ {
+ fprintf(stderr, "mcpc server stopped with status %d\n", (int)status);
+ }
tiny_mcp_tools_context_deactivate(context);
mcpc_server_close(server);
tiny_mcp_tools_context_free(context);
diff --git a/src/mcp/tools/common/common.c b/src/mcp/tools/common/common.c
index 71b6f68..d6669f8 100644
--- a/src/mcp/tools/common/common.c
+++ b/src/mcp/tools/common/common.c
@@ -104,7 +104,8 @@ bool
tiny_mcp_tool_add_property(mcpc_tool_t *tool,
const char *name,
const char *description,
- mcpc_anytype_t type)
+ mcpc_anytype_t type,
+ bool required)
{
mcpc_toolprop_t *property
= mcpc_toolprop_new2((const char8_t *)name, (const char8_t *)description, type);
@@ -112,6 +113,10 @@ tiny_mcp_tool_add_property(mcpc_tool_t *tool,
{
return false;
}
+ if (mcpc_toolprop_set_required(property, required) != MCPC_EC_0)
+ {
+ return false;
+ }
mcpc_tool_add_toolprop(tool, property);
return true;
}
diff --git a/src/mcp/tools/common/common.h b/src/mcp/tools/common/common.h
index 6b1cc96..07caea1 100644
--- a/src/mcp/tools/common/common.h
+++ b/src/mcp/tools/common/common.h
@@ -14,6 +14,7 @@ mcpc_tool_t *tiny_mcp_tool_new(const char *name, const char *description, mcpc_t
bool tiny_mcp_tool_add_property(mcpc_tool_t *tool,
const char *name,
const char *description,
- mcpc_anytype_t type);
+ mcpc_anytype_t type,
+ bool required);
#endif
diff --git a/src/mcp/tools/delete_message.c b/src/mcp/tools/delete_message.c
index ae924d7..8b6e8f9 100644
--- a/src/mcp/tools/delete_message.c
+++ b/src/mcp/tools/delete_message.c
@@ -14,14 +14,15 @@ call_delete_message(const mcpc_tool_t *tool, mcpc_ucbr_t *result)
char *message_id = tiny_mcp_tool_get_string(tool, "message_id", TINY_MESSAGE_ID_LENGTH);
char *session = tiny_mcp_tool_get_string(tool, "session_id", TINY_SESSION_ID_MAX);
tiny_store_status_t status;
- if (!context || !message_id || !session || !tiny_message_id_valid(message_id))
+ if (!context || !message_id || !tiny_message_id_valid(message_id))
{
tiny_mcp_tool_add_error(
result, "VALIDATION_ERROR", "message_id must be a canonical 26-character ULID");
}
else
{
- status = tiny_store_delete(context->store, message_id, session[0] ? session : NULL);
+ status
+ = tiny_store_delete(context->store, message_id, session && session[0] ? session : NULL);
if (status != TINY_STORE_OK && status != TINY_STORE_NOT_FOUND)
{
tiny_mcp_tool_add_error(
@@ -43,8 +44,7 @@ bool
tiny_mcp_register_delete_message(mcpc_server_t *server)
{
mcpc_tool_t *tool = tiny_mcp_tool_new("delete_message", "Delete a message", call_delete_message);
- return tool && tiny_mcp_tool_add_property(tool, "message_id", "Message ULID", MCPC_U8STR)
- && tiny_mcp_tool_add_property(
- tool, "session_id", "Session ID; empty if omitted", MCPC_U8STR)
+ return tool && tiny_mcp_tool_add_property(tool, "message_id", "Message ULID", MCPC_U8STR, true)
+ && tiny_mcp_tool_add_property(tool, "session_id", "Optional session ID", MCPC_U8STR, false)
&& mcpc_server_add_tool(server, tool) == MCPC_EC_0;
}
diff --git a/src/mcp/tools/read_messages.c b/src/mcp/tools/read_messages.c
index 55696d6..5a9e49f 100644
--- a/src/mcp/tools/read_messages.c
+++ b/src/mcp/tools/read_messages.c
@@ -75,6 +75,6 @@ bool
tiny_mcp_register_read_messages(mcpc_server_t *server)
{
mcpc_tool_t *tool = tiny_mcp_tool_new("read_messages", "Read a session", call_read_messages);
- return tool && tiny_mcp_tool_add_property(tool, "session_id", "Session ID", MCPC_U8STR)
+ return tool && tiny_mcp_tool_add_property(tool, "session_id", "Session ID", MCPC_U8STR, true)
&& mcpc_server_add_tool(server, tool) == MCPC_EC_0;
}
diff --git a/src/mcp/tools/search_messages.c b/src/mcp/tools/search_messages.c
index 166c167..d20d7b2 100644
--- a/src/mcp/tools/search_messages.c
+++ b/src/mcp/tools/search_messages.c
@@ -45,14 +45,15 @@ call_search_messages(const mcpc_tool_t *tool, mcpc_ucbr_t *result)
tiny_mcp_tools_context_t *context = tiny_mcp_tools_context_current();
char *query = tiny_mcp_tool_get_string(tool, "query", TINY_QUERY_MAX);
char *session = tiny_mcp_tool_get_string(tool, "session_id", TINY_SESSION_ID_MAX);
- int32_t requested_limit = 0;
+ int32_t requested_limit = 20;
+ mcpc_errcode_t limit_status
+ = mcpc_tool_get_tpropval_i32(tool, (const char8_t *)"limit", &requested_limit);
size_t limit, index, count = 0, candidate_count = 0, *candidates = NULL;
result_item_t *items = NULL;
tiny_json_buffer_t json;
- bool valid
- = context && query && query[0] && session
- && mcpc_tool_get_tpropval_i32(tool, (const char8_t *)"limit", &requested_limit) == MCPC_EC_0
- && requested_limit >= 1 && requested_limit <= 100;
+ bool valid = context && query && query[0]
+ && (limit_status == MCPC_EC_0 || limit_status == MCPC_EC_ANYDATA_NOTFOUND)
+ && requested_limit >= 1 && requested_limit <= 100;
if (!valid)
{
tiny_mcp_tool_add_error(result, "VALIDATION_ERROR", "query or limit is invalid");
@@ -85,7 +86,7 @@ call_search_messages(const mcpc_tool_t *tool, mcpc_ucbr_t *result)
= tiny_store_entry(context->store, candidates[index]);
double score;
if (!entry || entry->deleted
- || (session[0] && strcmp(entry->message.session_id, session) != 0))
+ || (session && session[0] && strcmp(entry->message.session_id, session) != 0))
{
continue;
}
@@ -127,8 +128,10 @@ bool
tiny_mcp_register_search_messages(mcpc_server_t *server)
{
mcpc_tool_t *tool = tiny_mcp_tool_new("search_messages", "Search messages", call_search_messages);
- return tool && tiny_mcp_tool_add_property(tool, "query", "Search text", MCPC_U8STR)
- && tiny_mcp_tool_add_property(tool, "session_id", "Session ID; empty for all", MCPC_U8STR)
- && tiny_mcp_tool_add_property(tool, "limit", "Result limit from 1 to 100", MCPC_I32)
+ return tool && tiny_mcp_tool_add_property(tool, "query", "Search text", MCPC_U8STR, true)
+ && tiny_mcp_tool_add_property(
+ tool, "session_id", "Optional session ID; omitted searches all", MCPC_U8STR, false)
+ && tiny_mcp_tool_add_property(
+ tool, "limit", "Optional result limit from 1 to 100; defaults to 20", MCPC_I32, false)
&& mcpc_server_add_tool(server, tool) == MCPC_EC_0;
}
diff --git a/src/mcp/tools/write_message.c b/src/mcp/tools/write_message.c
index 1d7ece2..f9c078a 100644
--- a/src/mcp/tools/write_message.c
+++ b/src/mcp/tools/write_message.c
@@ -162,8 +162,8 @@ call_write_message(const mcpc_tool_t *tool, mcpc_ucbr_t *result)
input.content = tiny_mcp_tool_get_string(tool, "content", TINY_CONTENT_MAX);
metadata = tiny_mcp_tool_get_string(tool, "metadata_json", TINY_CONTENT_MAX);
bool valid = context && input.session_id && input.session_id[0] && role && input.content
- && input.content[0] && metadata && tiny_role_parse(role, &input.role)
- && parse_metadata(metadata, &input);
+ && input.content[0] && tiny_role_parse(role, &input.role)
+ && parse_metadata(metadata ? metadata : "{}", &input);
if (!valid)
{
tiny_mcp_tool_add_error(result, "VALIDATION_ERROR", "invalid write_message arguments");
@@ -204,10 +204,13 @@ tiny_mcp_register_write_message(mcpc_server_t *server)
{
mcpc_tool_t *tool
= tiny_mcp_tool_new("write_message", "Append a conversation message", call_write_message);
- return tool && tiny_mcp_tool_add_property(tool, "session_id", "Session ID", MCPC_U8STR)
- && tiny_mcp_tool_add_property(tool, "role", "user, assistant, or system", MCPC_U8STR)
- && tiny_mcp_tool_add_property(tool, "content", "Message text", MCPC_U8STR)
- && tiny_mcp_tool_add_property(
- tool, "metadata_json", "Metadata object JSON; use {} when empty", MCPC_U8STR)
+ return tool && tiny_mcp_tool_add_property(tool, "session_id", "Session ID", MCPC_U8STR, true)
+ && tiny_mcp_tool_add_property(tool, "role", "user, assistant, or system", MCPC_U8STR, true)
+ && tiny_mcp_tool_add_property(tool, "content", "Message text", MCPC_U8STR, true)
+ && tiny_mcp_tool_add_property(tool,
+ "metadata_json",
+ "Optional metadata object JSON; defaults to {}",
+ MCPC_U8STR,
+ false)
&& mcpc_server_add_tool(server, tool) == MCPC_EC_0;
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ad314eb..fd9ac9e 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -22,6 +22,24 @@ if(MSVC)
endif()
add_test(NAME tiny-convmem-mcp.domain COMMAND tiny-convmem-domain-test)
+add_executable(tiny-convmem-mcpc-test mcpc_test.c)
+target_include_directories(tiny-convmem-mcpc-test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
+target_compile_features(tiny-convmem-mcpc-test PRIVATE c_std_17)
+target_link_libraries(tiny-convmem-mcpc-test PRIVATE mcpc::mcpc)
+if(MSVC)
+ target_compile_options(tiny-convmem-mcpc-test PRIVATE /utf-8 /W4)
+endif()
+add_test(NAME tiny-convmem-mcp.mcpc COMMAND tiny-convmem-mcpc-test)
+
+find_package(Python3 REQUIRED COMPONENTS Interpreter)
+add_test(
+ NAME tiny-convmem-mcp.protocol
+ COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/mcp_protocol_test.py"
+ --server "$<TARGET_FILE:tiny-convmem-mcp>"
+ --iterations 1000
+)
+set_tests_properties(tiny-convmem-mcp.protocol PROPERTIES TIMEOUT 120)
+
add_executable(tiny-convmem-boundary-test
boundary_test.c
"${PROJECT_SOURCE_DIR}/src/mcp/json.c"
@@ -93,6 +111,8 @@ if(TINY_CONVMEM_ENABLE_ASAN AND MSVC)
TEST
tiny-convmem-mcp.smoke
tiny-convmem-mcp.domain
+ tiny-convmem-mcp.mcpc
+ tiny-convmem-mcp.protocol
tiny-convmem-mcp.boundaries
tiny-convmem-mcp.allocator
tiny-convmem-mcp.encoding
diff --git a/tests/mcp_protocol_test.py b/tests/mcp_protocol_test.py
new file mode 100644
index 0000000..118043f
--- /dev/null
+++ b/tests/mcp_protocol_test.py
@@ -0,0 +1,236 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import argparse
+import json
+import shutil
+import subprocess
+import tempfile
+import threading
+from collections.abc import Mapping
+from pathlib import Path
+from typing import Any
+
+
+class Server:
+ def __init__(self, executable: Path, data_dir: Path) -> None:
+ self.stderr: list[str] = []
+ self.process = subprocess.Popen(
+ [str(executable), "--data-dir", str(data_dir)],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf-8",
+ bufsize=1,
+ )
+ assert self.process.stdin and self.process.stdout and self.process.stderr
+ self._stderr_thread = threading.Thread(target=self._drain_stderr, daemon=True)
+ self._stderr_thread.start()
+
+ def _drain_stderr(self) -> None:
+ assert self.process.stderr
+ self.stderr.extend(self.process.stderr)
+
+ def send(self, message: Mapping[str, Any] | str) -> dict[str, Any]:
+ assert self.process.stdin and self.process.stdout
+ line = message if isinstance(message, str) else json.dumps(message, separators=(",", ":"))
+ self.process.stdin.write(line + "\n")
+ self.process.stdin.flush()
+ response_line = self.process.stdout.readline()
+ if not response_line:
+ raise AssertionError(
+ f"server closed: returncode={self.process.poll()} stderr={''.join(self.stderr)[-4000:]}"
+ )
+ response = json.loads(response_line)
+ assert response.get("jsonrpc") == "2.0", response
+ return response
+
+ def notify(self, message: Mapping[str, Any]) -> None:
+ assert self.process.stdin
+ self.process.stdin.write(json.dumps(message, separators=(",", ":")) + "\n")
+ self.process.stdin.flush()
+
+ def close(self) -> None:
+ if self.process.stdin:
+ self.process.stdin.close()
+ returncode = self.process.wait(timeout=15)
+ self._stderr_thread.join(timeout=2)
+ assert returncode == 0, f"returncode={returncode} stderr={''.join(self.stderr)}"
+ stderr = "".join(self.stderr)
+ assert "AddressSanitizer" not in stderr and "ERROR:" not in stderr, stderr
+
+
+def request(request_id: int, method: str, params: Mapping[str, Any] | None = None) -> dict[str, Any]:
+ message: dict[str, Any] = {"jsonrpc": "2.0", "id": request_id, "method": method}
+ if params is not None:
+ message["params"] = params
+ return message
+
+
+def tool_call(server: Server, request_id: int, name: str, arguments: Mapping[str, Any]) -> dict[str, Any]:
+ response = server.send(
+ request(request_id, "tools/call", {"name": name, "arguments": arguments})
+ )
+ assert response.get("id") == request_id, response
+ return response
+
+
+def tool_payload(response: Mapping[str, Any], expect_error: bool = False) -> dict[str, Any]:
+ result = response["result"]
+ assert bool(result.get("isError", False)) is expect_error, response
+ return json.loads(result["content"][0]["text"])
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--server", type=Path, required=True)
+ parser.add_argument("--iterations", type=int, default=1000)
+ args = parser.parse_args()
+ data_dir = Path(tempfile.mkdtemp(prefix="tiny-convmem-p0-"))
+ server = Server(args.server.resolve(), data_dir)
+ next_id = 1
+ try:
+ response = server.send(
+ request(
+ next_id,
+ "initialize",
+ {
+ "protocolVersion": "2024-11-05",
+ "capabilities": {},
+ "clientInfo": {"name": "p0-regression", "version": "1"},
+ },
+ )
+ )
+ assert response["result"]["protocolVersion"] == "2024-11-05"
+ next_id += 1
+ server.notify({"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}})
+
+ listed = server.send(request(next_id, "tools/list", {}))
+ assert listed["id"] == next_id # also proves the notification emitted no response
+ next_id += 1
+ tools = {item["name"]: item for item in listed["result"]["tools"]}
+ assert set(tools) == {
+ "write_message",
+ "read_messages",
+ "search_messages",
+ "delete_message",
+ "rebuild_index",
+ "get_system_health",
+ }
+ assert tools["write_message"]["inputSchema"]["required"] == [
+ "session_id",
+ "role",
+ "content",
+ ]
+ assert tools["read_messages"]["inputSchema"]["required"] == ["session_id"]
+ assert tools["search_messages"]["inputSchema"]["required"] == ["query"]
+ assert tools["search_messages"]["inputSchema"]["properties"]["limit"]["type"] == "integer"
+ assert tools["delete_message"]["inputSchema"]["required"] == ["message_id"]
+
+ message_ids: list[str] = []
+ for index in range(20):
+ payload = tool_payload(
+ tool_call(
+ server,
+ next_id,
+ "write_message",
+ {"session_id": "target", "role": "user", "content": f"needle target {index}"},
+ )
+ )
+ next_id += 1
+ message_ids.append(payload["message"]["message_id"])
+ for index in range(10):
+ tool_payload(
+ tool_call(
+ server,
+ next_id,
+ "write_message",
+ {"session_id": "other", "role": "user", "content": f"needle other {index}"},
+ )
+ )
+ next_id += 1
+
+ history = tool_payload(
+ tool_call(server, next_id, "read_messages", {"session_id": "target"})
+ )["history"]
+ next_id += 1
+ assert len(history) == 20
+
+ for index in range(args.iterations):
+ arguments: dict[str, Any] = {"query": "needle"}
+ if index % 2:
+ arguments["session_id"] = "target"
+ results = tool_payload(
+ tool_call(server, next_id, "search_messages", arguments)
+ )["results"]
+ next_id += 1
+ assert len(results) == 20
+ if index % 2:
+ assert all(item["session_id"] == "target" for item in results)
+ if (index + 1) % 100 == 0:
+ print(f"completed {index + 1}/{args.iterations} alternating searches", flush=True)
+
+ assert tool_payload(
+ tool_call(server, next_id, "search_messages", {"query": "needle", "limit": 0}), True
+ )["status"] == "ERROR"
+ next_id += 1
+ assert tool_payload(
+ tool_call(server, next_id, "search_messages", {"query": "needle", "limit": 101}), True
+ )["status"] == "ERROR"
+ next_id += 1
+ fractional = tool_call(
+ server, next_id, "search_messages", {"query": "needle", "limit": 1.5}
+ )
+ assert fractional["error"]["code"] == -32602
+ next_id += 1
+ assert tool_payload(tool_call(server, next_id, "search_messages", {}), True)["status"] == "ERROR"
+ next_id += 1
+
+ assert tool_payload(
+ tool_call(server, next_id, "delete_message", {"message_id": message_ids[0]})
+ )["status"] == "OK"
+ next_id += 1
+ tool_payload(tool_call(server, next_id, "rebuild_index", {}))
+ next_id += 1
+ assert tool_payload(tool_call(server, next_id, "get_system_health", {}))["status"] == "OK"
+ next_id += 1
+
+ assert server.send("{bad}")["error"]["code"] == -32700
+ invalid = server.send({"id": next_id, "method": "tools/list"})
+ assert invalid["error"]["code"] == -32600
+ next_id += 1
+ missing_method = server.send({"jsonrpc": "2.0", "id": next_id})
+ assert missing_method["error"]["code"] == -32600
+ next_id += 1
+ null_id = server.send(
+ {"jsonrpc": "2.0", "id": None, "method": "tools/list", "params": {}}
+ )
+ assert null_id["error"]["code"] == -32600 and null_id["id"] is None
+ invalid_params = server.send(
+ {"jsonrpc": "2.0", "id": next_id, "method": "tools/list", "params": []}
+ )
+ assert invalid_params["error"]["code"] == -32602
+ next_id += 1
+ unknown_method = server.send(request(next_id, "unknown/method", {}))
+ assert unknown_method["error"]["code"] == -32601
+ next_id += 1
+ unknown_tool = tool_call(server, next_id, "unknown_tool", {})
+ assert unknown_tool["error"]["code"] == -32602
+ next_id += 1
+ missing_arguments = server.send(
+ request(next_id, "tools/call", {"name": "search_messages"})
+ )
+ assert missing_arguments["error"]["code"] == -32602
+ except Exception:
+ if server.process.poll() is None:
+ server.process.kill()
+ server.process.wait(timeout=5)
+ raise
+ else:
+ server.close()
+ finally:
+ shutil.rmtree(data_dir, ignore_errors=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/mcpc_test.c b/tests/mcpc_test.c
new file mode 100644
index 0000000..b0d317e
--- /dev/null
+++ b/tests/mcpc_test.c
@@ -0,0 +1,41 @@
+#include <mcpc/mcpc.h>
+
+#include <stdio.h>
+
+#include "support/test.h"
+
+static void
+empty_callback(const mcpc_tool_t *tool, mcpc_ucbr_t *result)
+{
+ (void)tool;
+ mcpc_toolcall_result_add_text_printf8(result, (const char8_t *)"{}");
+}
+
+int
+main(void)
+{
+ tiny_test_state_t state = { "mcpc fork", 0, 0 };
+ FILE *input = tmpfile();
+ FILE *output = tmpfile();
+ mcpc_server_t *server;
+ mcpc_tool_t *tool;
+ mcpc_toolprop_t *property;
+ TINY_EXPECT(&state, input != NULL && output != NULL);
+ server = mcpc_server_new_iostrm(input, output);
+ TINY_EXPECT(&state, server != NULL);
+ tool = mcpc_tool_new2((const char8_t *)"test", (const char8_t *)"test tool");
+ TINY_EXPECT(&state, tool != NULL);
+ TINY_EXPECT(&state, mcpc_server_add_tool(server, tool) == MCPC_EC_BUG);
+ TINY_EXPECT(&state, mcpc_tool_set_call_cb(tool, empty_callback) == MCPC_EC_0);
+
+ property = mcpc_toolprop_new2((const char8_t *)"limit", (const char8_t *)"limit", MCPC_I32);
+ TINY_EXPECT(&state, property != NULL && mcpc_toolprop_is_required(property));
+ TINY_EXPECT(&state, mcpc_toolprop_set_required(property, false) == MCPC_EC_0);
+ TINY_EXPECT(&state, !mcpc_toolprop_is_required(property));
+ mcpc_tool_add_toolprop(tool, property);
+ TINY_EXPECT(&state, mcpc_server_add_tool(server, tool) == MCPC_EC_0);
+ TINY_EXPECT(&state, mcpc_server_close(server) == MCPC_EC_0);
+ fclose(input);
+ fclose(output);
+ return tiny_test_finish(&state);
+}
diff --git a/third_party/mcpc b/third_party/mcpc
deleted file mode 160000
-Subproject 4025fddfedcfa7573803a47236b147733fcb3c0
diff --git a/third_party/mcpc/.github/workflows/mcpc.yml b/third_party/mcpc/.github/workflows/mcpc.yml
new file mode 100644
index 0000000..832395b
--- /dev/null
+++ b/third_party/mcpc/.github/workflows/mcpc.yml
@@ -0,0 +1,112 @@
+name: mcpc
+run-name: mcpc
+on: [push]
+jobs:
+ x64-linux-gcc10-glibc231:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - run: ls -l .
+ - run: sudo apt-get install -y buildah podman
+ - run: buildah from --name wctn docker.io/library/debian:bullseye
+ - run: buildah run wctn -- apt-get update
+ - run: buildah run wctn -- apt-get install -y sudo bash gcc make
+ - run: buildah copy wctn ../mcpc /mcpc
+ - run: buildah run wctn -- bash -c "cd /mcpc && CC=gcc make tst"
+ - run: buildah run wctn -- bash -c "cd /mcpc && CC=gcc make install"
+ - run: buildah run wctn -- bash -c "ls -l /usr/local/lib"
+ x64-linux-gcc12-glibc236:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - run: ls -l .
+ - run: sudo apt-get install -y buildah podman
+ - run: buildah from --name wctn docker.io/library/debian:bookworm
+ - run: buildah run wctn -- apt-get update
+ - run: buildah run wctn -- apt-get install -y sudo bash gcc make
+ - run: buildah copy wctn ../mcpc /mcpc
+ - run: buildah run wctn -- bash -c "cd /mcpc && CC=gcc make tst"
+ - run: buildah run wctn -- bash -c "cd /mcpc && CC=gcc make install"
+ - run: buildah run wctn -- bash -c "ls -l /usr/local/lib"
+ x64-linux-gcc14-glibc240:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - run: ls -l .
+ - run: sudo apt-get install -y buildah podman
+ - run: buildah from --name wctn docker.io/library/fedora:41
+ - run: buildah run wctn -- dnf install -y bash gcc make
+ - run: buildah copy wctn ../mcpc /mcpc
+ - run: buildah run wctn -- bash -c "cd /mcpc && CC=gcc make tst"
+ - run: buildah run wctn -- bash -c "cd /mcpc && CC=gcc make install"
+ - run: buildah run wctn -- bash -c "ls -l /usr/local/lib"
+ x64-win2022-vs2022:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: windows-2022
+ steps:
+ - uses: actions/checkout@v4
+ - shell: cmd
+ run: ${{ '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && dir' }}
+ - shell: cmd
+ run: ${{ '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && make' }}
+ - shell: cmd
+ run: ${{ '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && make tst' }}
+ - run: dir tst/itst-svcalc.exe
+ - run: ldd tst/itst-svcalc.exe
+ x64-win2025-vs2022:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: windows-2025
+ steps:
+ - uses: actions/checkout@v4
+ - shell: cmd
+ run: ${{ '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && dir' }}
+ - shell: cmd
+ run: ${{ '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && make' }}
+ - shell: cmd
+ run: ${{ '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && make tst' }}
+ - run: dir tst/itst-svcalc.exe
+ - run: ldd tst/itst-svcalc.exe
+ x64-win2022-gcc:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: windows-2022
+ steps:
+ - uses: actions/checkout@v4
+ - run: gcc --version
+ - run: make --version
+ - run: make tst
+ - run: ldd tst/itst-svcalc.out
+ x64-win2025-gcc:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: windows-2025
+ steps:
+ - uses: actions/checkout@v4
+ - run: gcc --version
+ - run: make --version
+ - run: make tst
+ - run: ldd tst/itst-svcalc.out
+ x64-mac13-clang:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: macos-13
+ steps:
+ - uses: actions/checkout@v4
+ - run: make tst
+ - run: otool -L tst/itst-svcalc.out
+ x64-mac14-clang:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: macos-14
+ steps:
+ - uses: actions/checkout@v4
+ - run: make tst
+ - run: otool -L tst/itst-svcalc.out
+ x64-mac15-clang:
+ if: contains(github.event.head_commit.message, 'NO_GH_CI') == false
+ runs-on: macos-15
+ steps:
+ - uses: actions/checkout@v4
+ - run: make tst
+ - run: otool -L tst/itst-svcalc.out
+
+
diff --git a/third_party/mcpc/.gitignore b/third_party/mcpc/.gitignore
new file mode 100644
index 0000000..cc32dfd
--- /dev/null
+++ b/third_party/mcpc/.gitignore
@@ -0,0 +1,30 @@
+*
+!.gitignore
+!**/
+
+!.github/workflows/*.yml
+
+!*.org
+!*.md
+
+!ver.txt
+!mcpc.pc
+!*.patch
+!*.c
+!*.h
+!*.py
+!*.bash
+!*.bat
+!*.mk
+!CMakeLists.txt
+!*help.txt
+!*.json
+!LICENSE*.txt
+
+!Makefile
+!.indent.pro
+
+!/AUTHORS
+!/ChangeLog
+!/COPYING
+!/README
diff --git a/third_party/mcpc/.indent.pro b/third_party/mcpc/.indent.pro
new file mode 100644
index 0000000..53ac612
--- /dev/null
+++ b/third_party/mcpc/.indent.pro
@@ -0,0 +1 @@
+--line-length120 \ No newline at end of file
diff --git a/third_party/mcpc/CMakeLists.txt b/third_party/mcpc/CMakeLists.txt
new file mode 100644
index 0000000..e3a330d
--- /dev/null
+++ b/third_party/mcpc/CMakeLists.txt
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
+
+project(mcpc)
+
+add_subdirectory(src)
+
+enable_testing()
+include(CTest)
+add_subdirectory(tst)
+
diff --git a/third_party/mcpc/Makefile b/third_party/mcpc/Makefile
new file mode 100644
index 0000000..d01f119
--- /dev/null
+++ b/third_party/mcpc/Makefile
@@ -0,0 +1,51 @@
+include mk/step0.mk
+
+subdirs =
+subdirs += src
+subdirs2 =
+subdirs2 += tst
+
+default: link
+
+clean:
+ $(if $(is_sh),@for el in $(subdirs); do $(MAKE) -C $$el $@; done)
+ $(if $(is_sh),@for el in $(subdirs2); do $(MAKE) -C $$el $@; done)
+ $(if $(is_cmd),@for %%e in ($(subdirs)) do $(MAKE) -C %%e -f $(firstword $(MAKEFILE_LIST)) $@)
+ $(if $(is_cmd),@for %%e in ($(subdirs2)) do $(MAKE) -C %%e -f $(firstword $(MAKEFILE_LIST)) $@)
+
+link:
+ $(if $(is_sh),@for el in $(subdirs); do $(MAKE) -C $$el $@; done)
+ $(if $(is_cmd),@for %%e in ($(subdirs)) do $(MAKE) -C %%e -f $(firstword $(MAKEFILE_LIST)) $@)
+
+tstcomp: link
+ $(if $(is_sh),@for el in $(subdirs2); do $(MAKE) -C $$el comp; done)
+ $(if $(is_cmd),@for %%e in ($(subdirs2)) do $(MAKE) -C %%e -f $(firstword $(MAKEFILE_LIST)) comp)
+
+tstlink: link
+ $(if $(is_sh),@for el in $(subdirs2); do $(MAKE) -C $$el link; done)
+ $(if $(is_cmd),@for %%e in ($(subdirs2)) do $(MAKE) -C %%e -f $(firstword $(MAKEFILE_LIST)) link)
+
+utst: link
+ $(if $(is_sh),@for el in $(subdirs2); do $(MAKE) -C $$el $@; done)
+ $(if $(is_cmd),@for %%e in ($(subdirs2)) do $(MAKE) -C %%e -f $(firstword $(MAKEFILE_LIST)) $@)
+
+itst: link
+ $(if $(is_sh),@for el in $(subdirs2); do $(MAKE) -C $$el $@; done)
+ $(if $(is_cmd),@for %%e in ($(subdirs2)) do $(MAKE) -C %%e -f $(firstword $(MAKEFILE_LIST)) $@)
+
+tst: utst itst
+
+PREFIX ?= /usr/local
+INC_DIR = $(PREFIX)/include
+LIB_DIR = $(PREFIX)/lib
+PC_DIR = $(LIB_DIR)/pkgconfig
+VER = $(shell cat src/ver.txt)
+
+install: tst
+ $(if $(is_sh), install -d $(INC_DIR)/mcpc $(LIB_DIR) $(PC_DIR) )
+ $(if $(is_sh), install -m 644 mcpc/* $(INC_DIR)/mcpc )
+ $(if $(is_sh), install -m 644 src/libmcpc.a $(LIB_DIR) )
+ $(if $(is_sh), install -m 755 src/libmcpc.so $(LIB_DIR)/libmcpc.so.$(VER) )
+ $(if $(is_sh), install -m 644 src/mcpc.pc $(PC_DIR) )
+ $(if $(is_sh), ln -sf $(LIB_DIR)/libmcpc.so.$(VER) $(LIB_DIR)/libmcpc.so )
+ $(if $(is_cmd), echo Automatic installation not supported on this platform )
diff --git a/third_party/mcpc/README.md b/third_party/mcpc/README.md
new file mode 100644
index 0000000..9466498
--- /dev/null
+++ b/third_party/mcpc/README.md
@@ -0,0 +1,474 @@
+
+# Table of Contents
+
+- [Overview](#overview)
+- [MCP Features](#features)
+- [Supported Platforms](#platforms)
+- [Modern C Support](#modernc)
+- [Building](#building)
+- [Development](#development)
+- [FAQ](#org73da0a6)
+- [License](#license)
+
+
+
+<a id="overview"></a>
+
+# Overview
+
+mcpc is a <b>M</b>odel <b>C</b>ontext <b>P</b>rotocol library in modern <b>C</b>. Its major goals are:
+
+- Provide the **most native** bridge between low-level infrastructure
+ and AI models
+- Explore the maximum capability of the **most recent** C programming language
+
+
+<a id="features"></a>
+
+# MCP Features
+
+<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
+
+
+<colgroup>
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+</colgroup>
+<thead>
+<tr>
+<th scope="col" class="org-left">Features</th>
+<th scope="col" class="org-left">Server</th>
+<th scope="col" class="org-left">Client</th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td class="org-left">Tool</td>
+<td class="org-left">✅</td>
+<td class="org-left">🔨</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Resource</td>
+<td class="org-left">✅</td>
+<td class="org-left">🔨</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Prompt</td>
+<td class="org-left">✅</td>
+<td class="org-left">🔨</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Completion</td>
+<td class="org-left">✅</td>
+<td class="org-left">🔨</td>
+</tr>
+
+
+<tr>
+<td class="org-left">STDIO Transport</td>
+<td class="org-left">✅</td>
+<td class="org-left">🔨</td>
+</tr>
+
+
+<tr>
+<td class="org-left">HTTP Transport</td>
+<td class="org-left">🔨</td>
+<td class="org-left">🔨</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Others</td>
+<td class="org-left">🔨</td>
+<td class="org-left">🔨</td>
+</tr>
+</tbody>
+</table>
+
+
+<a id="platforms"></a>
+
+# Supported Platforms
+
+<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
+
+
+<colgroup>
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+</colgroup>
+<thead>
+<tr>
+<th scope="col" class="org-left">&#xa0;</th>
+<th scope="col" class="org-left">Linux</th>
+<th scope="col" class="org-left">Windows</th>
+<th scope="col" class="org-left">macOS</th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td class="org-left">Linux</td>
+<td class="org-left">✅</td>
+<td class="org-left">✅</td>
+<td class="org-left">✅</td>
+</tr>
+</tbody>
+</table>
+
+Note: this table roughly shows the state of supporting on some platforms,
+for more detailed information, check the *Modern C Support*.
+
+
+<a id="modernc"></a>
+
+# Modern C Support
+
+Since mcpc claims to implement everything with the most modern C
+possible, its building process becomes a little different than many
+existing building systems or process, the major
+difference is that: ****we try to support as many C compilers as possible,
+even on one platform****.
+
+Ideally mcpc will be implemented fully in C23(currently the newest
+standard), but if a full C23 is missing on a platform, we
+either downgrade the C standard, or give up the support.
+
+This approach certainly has some benefits and consequences:
+
+1. The library can launch on as many platforms as possible, and be
+ able to compiled by as many C compilers as possible.
+2. There must be a minimum fallback version of C standard that the
+ library targets, currently it is C11.
+3. C23 features that are not patchable, or require non-trivial amount
+ of effort to patch, will not be used, e.g., [N2508](https://open-std.org/JTC1/SC22/WG14/www/docs/n2508.pdf), [N2645](https://open-std.org/JTC1/SC22/WG14/www/docs/n2645.pdf), etc.
+4. All safety checks and performance optimization will only focus on the
+ platforms with full C23 support.
+
+
+## Support Matrix
+
+`✅✅` : *implemented in C23*
+
+`🔨✅` : *implemented in C11~C17*
+
+`-` : *the platform does not exist, or work in progress*
+
+Linux:
+
+<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
+
+
+<colgroup>
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+</colgroup>
+<thead>
+<tr>
+<th scope="col" class="org-left">&#xa0;</th>
+<th scope="col" class="org-left">GLIBC 2.31</th>
+<th scope="col" class="org-left">GLIBC 2.36</th>
+<th scope="col" class="org-left">GLIBC 2.40</th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td class="org-left">GCC 10</td>
+<td class="org-left">🔨✅ (Debian 11)</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">GCC 12</td>
+<td class="org-left">-</td>
+<td class="org-left">🔨✅ (Debian 12)</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">GCC 13</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">GCC 14</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+<td class="org-left">✅✅ (Fedora 41)</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Clang 14</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Clang 16</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Clang 18</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Clang 19</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+<td class="org-left">✅✅ (Fedora 41)</td>
+</tr>
+</tbody>
+</table>
+
+Windows:
+
+<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
+
+
+<colgroup>
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+</colgroup>
+<thead>
+<tr>
+<th scope="col" class="org-left">&#xa0;</th>
+<th scope="col" class="org-left">CMD/PS</th>
+<th scope="col" class="org-left">Cygwin</th>
+<th scope="col" class="org-left">MinGW</th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td class="org-left">MSVC 19</td>
+<td class="org-left">🔨✅</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Clang 19</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">GCC 14</td>
+<td class="org-left">-</td>
+<td class="org-left">✅✅</td>
+<td class="org-left">✅✅</td>
+</tr>
+</tbody>
+</table>
+
+macOS:
+
+<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
+
+
+<colgroup>
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+
+<col class="org-left" />
+</colgroup>
+<thead>
+<tr>
+<th scope="col" class="org-left">&#xa0;</th>
+<th scope="col" class="org-left">macOS 13</th>
+<th scope="col" class="org-left">macOS 14</th>
+<th scope="col" class="org-left">macOS 15</th>
+</tr>
+</thead>
+
+<tbody>
+<tr>
+<td class="org-left">Clang 14</td>
+<td class="org-left">🔨✅</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+
+
+<tr>
+<td class="org-left">Clang 16</td>
+<td class="org-left">-</td>
+<td class="org-left">🔨✅</td>
+<td class="org-left">🔨✅</td>
+</tr>
+
+
+<tr>
+<td class="org-left">GCC 14</td>
+<td class="org-left">🔨✅</td>
+<td class="org-left">-</td>
+<td class="org-left">-</td>
+</tr>
+</tbody>
+</table>
+
+
+<a id="building"></a>
+
+# Building
+
+
+## Building (Linux, Cygwin, MSYS2, macOS, etc.)
+
+Prerequisites:
+
+- GNU Make
+
+Then
+
+ cd mcpc
+ make && make tst && make install
+
+
+## Building (Windows CMD/PS/VS)
+
+Prerequisites:
+
+- Visual Studio
+- GNU Make (`winget install ezwinports.make`)
+
+Two options to build:
+
+
+### 1. Build in CMD/PS
+
+Open "x64 Native Tools Command Prompt", then
+
+ cd mcpc
+ make && make tst
+
+
+### 2. Build in Visual Studio:
+
+"File" - "Open" - "CMake", "Build" - "Build All"
+
+> This approach is only for the better debugging experience on Windows,
+> *"Build in CMD/PS"* takes priority over this one.
+
+
+<a id="development"></a>
+
+# Development
+
+API reference can be found at [api.md](./misc/api.md).
+
+Dependencies:
+
+- [mjson](https://github.com/cesanta/mjson) (already in-tree)
+
+
+## Code Contributing
+
+Despite we claim to use modern C, as standard as possible, and as
+modern as possible, we have some extra conventions, in order to render
+our code more readable, inclusive and scalable:
+
+1. Unless mentioned explicitly, we follow GNU C [coding style](https://www.gnu.org/prep/standards/html_node/Writing-C.html).
+2. Prefer functions over macros, use macros only if necessary.
+3. The type of variables should be reflected in the source code. Use of
+ keyword `auto` is thus forbidden.
+4. Names of defined variables that are exposed publicly should be in
+ uppercase, otherwise in lowercase.
+
+
+<a id="org73da0a6"></a>
+
+# FAQ
+
+
+## Should I prefer mcpc over other SDKs?
+
+It depends on your use case. Roughly speaking, if
+
+1. you prefer your MCP servers or clients to perform tasks in the most
+ native way (e.g. manually manage memory allocation, equipped with
+ competitive performance, etc.).
+2. you wish your MCP servers or clients are distributed with minimum
+ software requirements (i.e. you don't want to force users to
+ have Python, Node.js or similar things installed on their machines)
+
+then mcpc is for you. Otherwise I would strongly recommend you consider
+Python, Javscript or other SDKs, they have more intuitive and
+easy-to-use high-level APIs for MCP protocol.
+
+
+<a id="license"></a>
+
+# License
+
+ The MIT License (MIT)
+
+ Copyright (c) 2025 Michael Lee <micl2e2 AT proton.me>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ the Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/third_party/mcpc/README.org b/third_party/mcpc/README.org
new file mode 100644
index 0000000..893f38d
--- /dev/null
+++ b/third_party/mcpc/README.org
@@ -0,0 +1,197 @@
+#+title: mcpc
+#+options: toc:1 num:nil
+
+* Overview
+:PROPERTIES:
+:CUSTOM_ID: overview
+:END:
+
+mcpc is a <b>M</b>odel <b>C</b>ontext <b>P</b>rotocol library in modern <b>C</b>. Its major goals are:
++ Provide the *most native* bridge between low-level infrastructure
+ and AI models
++ Explore the maximum capability of the *most recent* C programming language
+
+* MCP Features
+:PROPERTIES:
+:CUSTOM_ID: features
+:END:
+
+| Features | Server | Client |
+|-----------------+--------+--------|
+| Tool | ✅ | 🔨 |
+| Resource | ✅ | 🔨 |
+| Prompt | ✅ | 🔨 |
+| Completion | ✅ | 🔨 |
+| STDIO Transport | ✅ | 🔨 |
+| HTTP Transport | 🔨 | 🔨 |
+| Others | 🔨 | 🔨 |
+
+
+* Supported Platforms
+:PROPERTIES:
+:CUSTOM_ID: platforms
+:END:
+
+| | Linux | Windows | macOS |
+|-------+-------+---------+-------|
+| Linux | ✅ | ✅ | ✅ |
+
+Note: this table roughly shows the state of supporting on some platforms,
+for more detailed information, check the /Modern C Support/.
+
+
+
+* Modern C Support
+:PROPERTIES:
+:CUSTOM_ID: modernc
+:END:
+
+Since mcpc claims to implement everything with the most modern C
+possible, its building process becomes a little different than many
+existing building systems or process, the major
+difference is that: **we try to support as many C compilers as possible,
+even on one platform**.
+
+Ideally mcpc will be implemented fully in C23(currently the newest
+standard), but if a full C23 is missing on a platform, we
+either downgrade the C standard, or give up the support.
+
+This approach certainly has some benefits and consequences:
+
+1. The library can launch on as many platforms as possible, and be
+ able to compiled by as many C compilers as possible.
+2. There must be a minimum fallback version of C standard that the
+ library targets, currently it is C11.
+3. C23 features that are not patchable, or require non-trivial amount
+ of effort to patch, will not be used, e.g., [[https://open-std.org/JTC1/SC22/WG14/www/docs/n2508.pdf][N2508]], [[https://open-std.org/JTC1/SC22/WG14/www/docs/n2645.pdf][N2645]], etc.
+4. All safety checks and performance optimization will only focus on the
+ platforms with full C23 support.
+
+** Support Matrix
+
+=✅✅= : /implemented in C23/
+
+=🔨✅= : /implemented in C11~C17/
+
+=-= : /the platform does not exist, or work in progress/
+
+Linux:
+
+| | GLIBC 2.31 | GLIBC 2.36 | GLIBC 2.40 |
+|----------+------------------+------------------+------------------|
+| GCC 10 | 🔨✅ (Debian 11) | - | - |
+| GCC 12 | - | 🔨✅ (Debian 12) | - |
+| GCC 13 | - | - | - |
+| GCC 14 | - | - | ✅✅ (Fedora 41) |
+| Clang 14 | - | - | - |
+| Clang 16 | - | - | - |
+| Clang 18 | - | - | - |
+| Clang 19 | - | - | ✅✅ (Fedora 41) |
+
+
+Windows:
+
+| | CMD/PS | Cygwin | MinGW |
+|----------+--------+--------+-------|
+| MSVC 19 | 🔨✅ | - | - |
+| Clang 19 | - | - | - |
+| GCC 14 | - | ✅✅ | ✅✅ |
+
+
+macOS:
+
+| | macOS 13 | macOS 14 | macOS 15 |
+|----------+----------+----------+----------|
+| Clang 14 | 🔨✅ | - | - |
+| Clang 16 | - | 🔨✅ | 🔨✅ |
+| GCC 14 | 🔨✅ | - | - |
+
+
+* Building
+:PROPERTIES:
+:CUSTOM_ID: building
+:END:
+
+** Building (Linux, Cygwin, MSYS2, macOS, etc.)
+
+Prerequisites:
+- GNU Make
+
+Then
+#+begin_src
+cd mcpc
+make && make tst && make install
+#+end_src
+
+** Building (Windows CMD/PS/VS)
+
+Prerequisites:
+- Visual Studio
+- GNU Make (=winget install ezwinports.make=)
+
+Two options to build:
+
+*** 1. Build in CMD/PS
+
+Open "x64 Native Tools Command Prompt", then
+
+#+begin_src
+cd mcpc
+make && make tst
+#+end_src
+
+*** 2. Build in Visual Studio:
+
+"File" - "Open" - "CMake", "Build" - "Build All"
+
+#+begin_quote
+This approach is only for the better debugging experience on Windows,
+/"Build in CMD/PS"/ takes priority over this one.
+#+end_quote
+
+
+* Development
+:PROPERTIES:
+:CUSTOM_ID: development
+:END:
+
+API reference can be found at [[./misc/api.md][api.md]].
+
+Dependencies:
+- [[https://github.com/cesanta/mjson][mjson]] (already in-tree)
+
+** Code Contributing
+
+Despite we claim to use modern C, as standard as possible, and as
+modern as possible, we have some extra conventions, in order to render
+our code more readable, inclusive and scalable:
+
+1. Unless mentioned explicitly, we follow GNU C [[https://www.gnu.org/prep/standards/html_node/Writing-C.html][coding style]].
+2. Prefer functions over macros, use macros only if necessary.
+3. The type of variables should be reflected in the source code. Use of
+ keyword =auto= is thus forbidden.
+4. Names of defined variables that are exposed publicly should be in
+ uppercase, otherwise in lowercase.
+
+
+* FAQ
+
+** Should I prefer mcpc over other SDKs?
+
+It depends on your use case. Roughly speaking, if
+1. you prefer your MCP servers or clients to perform tasks in the most
+ native way (e.g. manually manage memory allocation, equipped with
+ competitive performance, etc.).
+2. you wish your MCP servers or clients are distributed with minimum
+ software requirements (i.e. you don't want to force users to
+ have Python, Node.js or similar things installed on their machines)
+then mcpc is for you. Otherwise I would strongly recommend you consider
+Python, Javscript or other SDKs, they have more intuitive and
+easy-to-use high-level APIs for MCP protocol.
+
+* License
+:PROPERTIES:
+:CUSTOM_ID: license
+:END:
+
+#+include: "LICENSE" src
diff --git a/third_party/mcpc/UPSTREAM.md b/third_party/mcpc/UPSTREAM.md
new file mode 100644
index 0000000..7951653
--- /dev/null
+++ b/third_party/mcpc/UPSTREAM.md
@@ -0,0 +1,12 @@
+# Vendored mcpc fork
+
+This directory is a project-maintained fork of
+<https://github.com/micl2e2/mcpc.git>.
+
+- Imported revision: `4025fddfedcfa7573803a47236b147733fcb3c07`
+- Upstream license: MIT (`LICENSE`)
+- Bundled mjson license: MIT (`licenses/LICENSE-mjson.txt`)
+
+The code is tracked directly by the parent repository rather than as a Git
+submodule. Upstream updates are reviewed and imported manually so the MCP
+2024-11-05 compatibility and stability patches in this fork remain auditable.
diff --git a/third_party/mcpc/licenses/LICENSE-mjson.txt b/third_party/mcpc/licenses/LICENSE-mjson.txt
new file mode 100644
index 0000000..cc47fea
--- /dev/null
+++ b/third_party/mcpc/licenses/LICENSE-mjson.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Cesanta Software Limited
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/mcpc/mcpc/_c23_keywords.h b/third_party/mcpc/mcpc/_c23_keywords.h
new file mode 100644
index 0000000..87a64f7
--- /dev/null
+++ b/third_party/mcpc/mcpc/_c23_keywords.h
@@ -0,0 +1,30 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#if defined(MCPC_C23PTCH_KW1)
+
+#define nullptr ((void*)0)
+#define constexpr
+#define bool _Bool
+#define true (1)
+#define false (0)
+
+#endif
diff --git a/third_party/mcpc/mcpc/_c23_stdckdint.h b/third_party/mcpc/mcpc/_c23_stdckdint.h
new file mode 100644
index 0000000..4aea8ac
--- /dev/null
+++ b/third_party/mcpc/mcpc/_c23_stdckdint.h
@@ -0,0 +1,28 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#if defined(MOCK_PTCH_C23CKD1)
+
+#define ckd_add(R, A, B) __builtin_add_overflow((A), (B), (R))
+#define ckd_sub(R, A, B) __builtin_sub_overflow((A), (B), (R))
+#define ckd_mul(R, A, B) __builtin_mul_overflow((A), (B), (R))
+
+#endif
diff --git a/third_party/mcpc/mcpc/_c23_uchar.h b/third_party/mcpc/mcpc/_c23_uchar.h
new file mode 100644
index 0000000..41fc382
--- /dev/null
+++ b/third_party/mcpc/mcpc/_c23_uchar.h
@@ -0,0 +1,31 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#if defined(MCPC_C23PTCH_UCHAR1)
+typedef char char8_t;
+#elif defined(MCPC_C23PTCH_UCHAR2)
+typedef unsigned char char8_t;
+#else
+#include <uchar.h>
+#endif
+
+typedef char8_t ch8_t;
+
diff --git a/third_party/mcpc/mcpc/anydata.h b/third_party/mcpc/mcpc/anydata.h
new file mode 100644
index 0000000..a3a0712
--- /dev/null
+++ b/third_party/mcpc/mcpc/anydata.h
@@ -0,0 +1,101 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_anydata
+#define h_pub_anydata
+
+#include <stdint.h>
+
+#include "_c23_keywords.h"
+
+enum mcpc_anytype
+{
+ MCPC_NONE = 0,
+ MCPC_I8,
+ MCPC_U8,
+ MCPC_I16,
+ MCPC_U16,
+ MCPC_I32,
+ MCPC_U32,
+ MCPC_I64,
+ MCPC_U64,
+ MCPC_U8STR,
+ MCPC_I8S,
+ MCPC_U8S,
+ MCPC_I16S,
+ MCPC_U16S,
+ MCPC_I32S,
+ MCPC_U32S,
+ MCPC_I64S,
+ MCPC_U64S,
+ MCPC_FUNC,
+};
+typedef enum mcpc_anytype mcpc_anytype_t;
+
+typedef struct mcpc_anydata mcpc_anydata_t;
+
+typedef struct mcpc_anypool mcpc_anypool_t;
+
+
+#ifdef MCPC_C23GIVUP_FIXENUM
+enum mcpc_mime
+#else
+enum mcpc_mime:uint16_t
+#endif
+{
+ MCPC_MIME_NONE = 0,
+ MCPC_MIME_TEXT_PLAIN,
+ MCPC_MIME_IMAGE_PNG,
+ MCPC_MIME_IMAGE_JPEG,
+ MCPC_MIME_IMAGE_GIF,
+ MCPC_MIME_AUDIO_MPEG,
+ MCPC_MIME_AUDIO_WAV,
+ MCPC_MIME_VIDEO_MPEG,
+ MCPC_MIME_VIDEO_AVI,
+ MCPC_MIME_APPLI_OCTS,
+ MCPC_MIME_APPLI_JSON,
+ MCPC_MIME_APPLI_PDF,
+};
+typedef enum mcpc_mime mcpc_mime_t;
+
+
+static inline bool
+mcpc_anytype_is_intlike (mcpc_anytype_t typ)
+{
+ if (typ == MCPC_I8 || typ == MCPC_I16 || typ == MCPC_I32 || typ == MCPC_I64 || typ == MCPC_U8 || typ == MCPC_U16
+ || typ == MCPC_U32 || typ == MCPC_U64 || typ == MCPC_FUNC)
+ return true;
+
+ return false;
+}
+
+static inline bool
+mcpc_anytype_is_arrlike (mcpc_anytype_t typ)
+{
+ if (typ == MCPC_U8STR || typ == MCPC_I8S || typ == MCPC_I16S || typ == MCPC_I32S || typ == MCPC_I64S
+ || typ == MCPC_U8S || typ == MCPC_U16S || typ == MCPC_U32S || typ == MCPC_U64S)
+ return true;
+
+ return false;
+}
+
+
+#endif
diff --git a/third_party/mcpc/mcpc/errcode.h b/third_party/mcpc/mcpc/errcode.h
new file mode 100644
index 0000000..8b15981
--- /dev/null
+++ b/third_party/mcpc/mcpc/errcode.h
@@ -0,0 +1,50 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_errcode
+#define h_pub_errcode
+
+enum mcpc_errcode
+{
+ MCPC_EC_0 = 0,
+ MCPC_EC_BUG,
+ MCPC_EC_BUFCAP,
+ MCPC_EC_ANYDATA_LONGMETA1,
+ MCPC_EC_ANYDATA_LONGMETA2,
+ MCPC_EC_LONGTOOLNAME,
+ MCPC_EC_LONGTOOLDESC,
+ MCPC_EC_LONGTPROPNAME,
+ MCPC_EC_LONGTPROPDESC,
+ MCPC_EC_TOOLPROP_NOTFOUND,
+ MCPC_EC_TOOLCALL_USERERR,
+ MCPC_EC_CONNPOOL_NOTADD,
+ MCPC_EC_SVIMPL_SOCKET,
+ MCPC_EC_SVIMPL_BIND,
+ MCPC_EC_SVIMPL_LISTEN,
+ MCPC_EC_SVIMPL_TCP_INSUF_BUF,
+ MCPC_EC_ANYDATA_NOTFOUND,
+ MCPC_EC_ANYDATA_LONGNAME,
+ MCPC_EC_PRMPTARG_NOTFOUND,
+ MCPC_EC_JSON_FIELD_INVALID,
+};
+typedef enum mcpc_errcode mcpc_errcode_t;
+
+#endif
diff --git a/third_party/mcpc/mcpc/mcpc.h b/third_party/mcpc/mcpc/mcpc.h
new file mode 100644
index 0000000..163fba0
--- /dev/null
+++ b/third_party/mcpc/mcpc/mcpc.h
@@ -0,0 +1,33 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_mcpc
+#define h_pub_mcpc
+
+#include "visb.h"
+#include "errcode.h"
+#include "tool.h"
+#include "rsc.h"
+#include "server.h"
+#include "ucbr.h"
+
+
+#endif
diff --git a/third_party/mcpc/mcpc/prmpt.h b/third_party/mcpc/mcpc/prmpt.h
new file mode 100644
index 0000000..e035c2f
--- /dev/null
+++ b/third_party/mcpc/mcpc/prmpt.h
@@ -0,0 +1,98 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_prmpt
+#define h_pub_prmpt
+
+#include <stddef.h>
+
+#include "_c23_uchar.h"
+#include "visb.h"
+
+#include "anydata.h"
+#include "errcode.h"
+#include "ucbr.h"
+
+typedef mcpc_anydata_t mcpc_prmptarghint_t;
+
+typedef mcpc_anydata_t mcpc_prmptarg_t;
+
+typedef mcpc_anydata_t mcpc_prmpt_t;
+
+typedef void (*mcpc_prmpt_callcb_t) (const mcpc_prmpt_t *prmpt, mcpc_ucbr_t *ucbr);
+
+MCPC_API mcpc_prmptarg_t *
+mcpc_prmptarg_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+
+MCPC_API mcpc_prmptarg_t *
+mcpc_prmptarg_new2 (const char8_t *nament, const char8_t *descnt);
+
+MCPC_API mcpc_prmptarg_t *
+mcpc_prmptarg_new3 (const char8_t *nament);
+
+MCPC_API mcpc_errcode_t
+mcpc_prmptarg_get_u8str (const mcpc_prmptarg_t *prmptarg, char8_t *ret, size_t ret_cap, size_t *ret_len);
+
+MCPC_API mcpc_errcode_t
+mcpc_prmptarg_getref_u8str (const mcpc_prmptarg_t *prmptarg, const char8_t **ret, size_t *ret_len);
+
+MCPC_API mcpc_errcode_t
+mcpc_prmptarg_add_hint_printf8 (mcpc_prmptarg_t *prmptarg, const char8_t *fmt, ...);
+
+MCPC_API mcpc_prmpt_t *
+mcpc_prmpt_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+
+MCPC_API mcpc_prmpt_t *
+mcpc_prmpt_new2 (const char8_t *nament, const char8_t *descnt);
+
+MCPC_API mcpc_prmpt_t *
+mcpc_prmpt_new3 (const char8_t *nament);
+
+MCPC_API mcpc_errcode_t
+mcpc_prmpt_addfre_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg);
+
+MCPC_API void
+mcpc_prmpt_addcpy_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg);
+
+#define mcpc_prmpt_add_prmptarg mcpc_prmpt_addfre_prmptarg
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_get_prmptarg (const mcpc_prmpt_t *prmpt, size_t idx);
+
+MCPC_API size_t
+mcpc_prmpt_get_prmptarg_len (const mcpc_prmpt_t *prmpt);
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_get_next_prmptarg (const mcpc_prmpt_t *prmpt);
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_getbyname_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_name, size_t parg_name_len);
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_getbynament_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_nament);
+
+MCPC_API mcpc_errcode_t
+mcpc_prmpt_get_callcb (const mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t *ret);
+
+MCPC_API mcpc_errcode_t
+mcpc_prmpt_set_callcb (mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t cb);
+
+#endif
diff --git a/third_party/mcpc/mcpc/rsc.h b/third_party/mcpc/mcpc/rsc.h
new file mode 100644
index 0000000..7c98bb2
--- /dev/null
+++ b/third_party/mcpc/mcpc/rsc.h
@@ -0,0 +1,60 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_rsc
+#define h_pub_rsc
+
+#include "_c23_uchar.h"
+#include "visb.h"
+
+#include "anydata.h"
+#include "errcode.h"
+
+typedef mcpc_anydata_t mcpc_rsc_t;
+
+MCPC_API mcpc_rsc_t *
+mcpc_rsc_new (const char8_t *uri, const size_t uri_len, const char8_t *name, const size_t name_len);
+
+MCPC_API mcpc_rsc_t *
+mcpc_rsc_new2 (const char8_t *uri_nt, const char8_t *nament);
+
+MCPC_API mcpc_rsc_t *
+mcpc_rsc_new3 (mcpc_mime_t mime, const char8_t *uri_nt, const char8_t *nament);
+
+MCPC_API void
+mcpc_rsc_free (mcpc_rsc_t *rsc);
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_get_uri (const mcpc_rsc_t *rsc, char8_t *ret, size_t ret_cap, size_t *ret_len);
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_getref_uri (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len);
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_getref_name (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len);
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_get_mime (const mcpc_rsc_t *rsc, mcpc_mime_t *ret);
+
+MCPC_API void
+mcpc_rsc_set_mime (mcpc_rsc_t *rsc, mcpc_mime_t mime);
+
+#endif
diff --git a/third_party/mcpc/mcpc/server.h b/third_party/mcpc/mcpc/server.h
new file mode 100644
index 0000000..2ed28eb
--- /dev/null
+++ b/third_party/mcpc/mcpc/server.h
@@ -0,0 +1,89 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_server
+#define h_pub_server
+
+#include <stdio.h>
+
+#include "visb.h"
+#include "_c23_keywords.h"
+
+#include "errcode.h"
+#include "tool.h"
+#include "prmpt.h"
+#include "rsc.h"
+
+
+typedef struct mcpc_server mcpc_server_t;
+
+MCPC_API mcpc_server_t *
+mcpc_server_new_iostrm (const FILE *const strm_in, const FILE *const strm_out);
+
+MCPC_API mcpc_server_t *
+mcpc_server_new_tcp ();
+
+MCPC_API mcpc_errcode_t
+mcpc_server_set_name (mcpc_server_t *sv, const char8_t *name, size_t name_len);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_set_nament (mcpc_server_t *sv, const char8_t *nament);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_prmt (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_prmt_listchg (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_tool (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_tool_listchg (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_rsc (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_rsc_subscr (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_rsc_listchg (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_complt (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_add_tool (mcpc_server_t *sv, mcpc_tool_t *tool);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_add_prmpt (mcpc_server_t *sv, mcpc_prmpt_t *prmpt);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_add_rsc (mcpc_server_t *sv, mcpc_rsc_t *rsc);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_start (mcpc_server_t *sv);
+
+MCPC_API mcpc_errcode_t
+mcpc_server_close (mcpc_server_t *sv);
+
+#endif
diff --git a/third_party/mcpc/mcpc/tool.h b/third_party/mcpc/mcpc/tool.h
new file mode 100644
index 0000000..931e914
--- /dev/null
+++ b/third_party/mcpc/mcpc/tool.h
@@ -0,0 +1,85 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_tool
+#define h_pub_tool
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "_c23_uchar.h"
+#include "visb.h"
+
+#include "anydata.h"
+#include "errcode.h"
+#include "ucbr.h"
+
+typedef mcpc_anydata_t mcpc_toolprop_t;
+
+typedef mcpc_anydata_t mcpc_tool_t;
+
+typedef void (*mcpc_tcallcb_t) (const mcpc_tool_t *tool, mcpc_ucbr_t *ucbr);
+
+MCPC_API mcpc_toolprop_t *
+mcpc_toolprop_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len, mcpc_anytype_t typ);
+
+MCPC_API mcpc_toolprop_t *
+mcpc_toolprop_new2 (const char8_t *nament, const char8_t *descnt, mcpc_anytype_t typ);
+
+MCPC_API bool
+mcpc_toolprop_is_required (const mcpc_toolprop_t *toolprop);
+
+MCPC_API mcpc_errcode_t
+mcpc_toolprop_set_required (mcpc_toolprop_t *toolprop, bool required);
+
+MCPC_API mcpc_tool_t *
+mcpc_tool_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+
+MCPC_API mcpc_tool_t *
+mcpc_tool_new2 (const char8_t *nament, const char8_t *descnt);
+
+MCPC_API void
+mcpc_tool_addfre_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop);
+
+MCPC_API void
+mcpc_tool_addcpy_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop);
+
+#define mcpc_tool_add_toolprop mcpc_tool_addfre_toolprop
+
+MCPC_API const mcpc_toolprop_t *
+mcpc_tool_get_toolprop (const mcpc_tool_t *tool, size_t idx);
+
+MCPC_API size_t
+mcpc_tool_get_toolprop_len (const mcpc_tool_t *tool);
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_get_tpropval_i32 (const mcpc_tool_t *tool, const char8_t *tprop_nament, int32_t *ret);
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_get_tpropval_u8str (const mcpc_tool_t *tool, const char8_t *tprop_nament, char8_t *ret, size_t ret_cap, size_t *ret_len);
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_get_call_cb (const mcpc_tool_t *tool, mcpc_tcallcb_t *ret);
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_set_call_cb (mcpc_tool_t *tool, mcpc_tcallcb_t cb);
+
+#endif
diff --git a/third_party/mcpc/mcpc/ucbr.h b/third_party/mcpc/mcpc/ucbr.h
new file mode 100644
index 0000000..03620ad
--- /dev/null
+++ b/third_party/mcpc/mcpc/ucbr.h
@@ -0,0 +1,55 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_ucbr
+#define h_pub_ucbr
+
+#include <stdarg.h>
+
+#include "_c23_uchar.h"
+#include "visb.h"
+
+#include "anydata.h"
+#include "errcode.h"
+
+typedef mcpc_anypool_t mcpc_ucbr_t;
+
+MCPC_API void
+mcpc_ucbr_toolcall_add_text_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+MCPC_API void
+mcpc_ucbr_toolcall_add_errmsg_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+MCPC_API void
+mcpc_ucbr_prmptget_add_user_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+MCPC_API void
+mcpc_ucbr_prmptget_add_assist_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+#define mcpc_toolcall_result_add_text_printf8 mcpc_ucbr_toolcall_add_text_printf8
+
+#define mcpc_toolcall_result_add_errmsg_printf8 mcpc_ucbr_toolcall_add_errmsg_printf8
+
+#define mcpc_prmptget_result_add_user_printf8 mcpc_ucbr_prmptget_add_user_printf8
+
+#define mcpc_prmptget_result_add_assist_printf8 mcpc_ucbr_prmptget_add_assist_printf8
+
+#endif
diff --git a/third_party/mcpc/mcpc/visb.h b/third_party/mcpc/mcpc/visb.h
new file mode 100644
index 0000000..6a96a1f
--- /dev/null
+++ b/third_party/mcpc/mcpc/visb.h
@@ -0,0 +1,38 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_pub_visb
+#define h_pub_visb
+
+/* *INDENT-OFF* */
+#if defined(_WIN32) && defined(MCPC_SHARED)
+/**/ #ifdef MCPC_EXPORTS
+/**//**/ #define MCPC_API __declspec(dllexport)
+/**/ #else
+/**//**/ #define MCPC_API __declspec(dllimport)
+/**/ #endif
+#else
+/**/ #define MCPC_API
+#endif
+/* *INDENT-ON* */
+
+
+#endif
diff --git a/third_party/mcpc/misc/api.md b/third_party/mcpc/misc/api.md
new file mode 100644
index 0000000..f5a9618
--- /dev/null
+++ b/third_party/mcpc/misc/api.md
@@ -0,0 +1,1112 @@
+
+# Table of Contents
+
+- [Server](#server)
+ - [`mcpc_server_t`](#=mcpc_server_t=)
+ - [`mcpc_server_new_iostrm`](#=mcpc_server_new_iostrm=)
+ - [`mcpc_server_new_tcp`](#=mcpc_server_new_tcp=)
+ - [`mcpc_server_set_nament`](#=mcpc_server_set_nament=)
+ - [`mcpc_server_capa_enable_prmt`](#=mcpc_server_capa_enable_prmt=)
+ - [`mcpc_server_capa_enable_prmt_listchg`](#=mcpc_server_capa_enable_prmt_listchg=)
+ - [`mcpc_server_capa_enable_tool`](#=mcpc_server_capa_enable_tool=)
+ - [`mcpc_server_capa_enable_tool_listchg`](#=mcpc_server_capa_enable_tool_listchg=)
+ - [`mcpc_server_capa_enable_rsc`](#=mcpc_server_capa_enable_rsc=)
+ - [`mcpc_server_capa_enable_rsc_subscr`](#=mcpc_server_capa_enable_rsc_subscr=)
+ - [`mcpc_server_capa_enable_rsc_listchg`](#=mcpc_server_capa_enable_rsc_listchg=)
+ - [`mcpc_server_capa_enable_complt`](#=mcpc_server_capa_enable_complt=)
+ - [`mcpc_server_add_tool`](#=mcpc_server_add_tool=)
+ - [`mcpc_server_add_prmpt`](#=mcpc_server_add_prmpt=)
+ - [`mcpc_server_add_rsc`](#=mcpc_server_add_rsc=)
+ - [`mcpc_server_start`](#=mcpc_server_start=)
+ - [`mcpc_server_close`](#=mcpc_server_close=)
+- [Tool](#org386a1c2)
+ - [`mcpc_tool_t`](#=mcpc_tool_t=)
+ - [`mcpc_toolprop_t`](#=mcpc_toolprop_t=)
+ - [`mcpc_tcallcb_t`](#=mcpc_tcallcb_t=)
+ - [`mcpc_toolprop_new`](#=mcpc_toolprop_new=)
+ - [`mcpc_toolprop_new2`](#=mcpc_toolprop_new2=)
+ - [`mcpc_toolprop_is_required`](#=mcpc_toolprop_is_required=)
+ - [`mcpc_toolprop_set_required`](#=mcpc_toolprop_set_required=)
+ - [`mcpc_tool_new`](#=mcpc_tool_new=)
+ - [`mcpc_tool_new2`](#=mcpc_tool_new2=)
+ - [`mcpc_tool_addfre_toolprop`](#=mcpc_tool_addfre_toolprop=)
+ - [`mcpc_tool_addcpy_toolprop`](#=mcpc_tool_addcpy_toolprop=)
+ - [`mcpc_tool_add_toolprop`](#=mcpc_tool_add_toolprop=)
+ - [`mcpc_tool_get_toolprop` (deprecate)](#=mcpc_tool_get_toolprop=)
+ - [`mcpc_tool_get_toolprop_len`](#=mcpc_tool_get_toolprop_len=)
+ - [`mcpc_tool_get_tpropval_i32`](#=mcpc_tool_get_tpropval_i32=)
+ - [`mcpc_tool_get_tpropval_u8str`](#=mcpc_tool_get_tpropval_u8str=)
+ - [`mcpc_tool_get_call_cb`](#=mcpc_tool_get_call_cb=)
+ - [`mcpc_tool_set_call_cb`](#=mcpc_tool_set_call_cb=)
+ - [`mcpc_toolcall_result_add_text_printf8`](#=mcpc_toolcall_result_add_text_printf8=)
+ - [`mcpc_toolcall_result_add_errmsg_printf8`](#=mcpc_toolcall_result_add_errmsg_printf8=)
+- [Resource](#org52ee5a7)
+ - [`mcpc_rsc_t`](#=mcpc_rsc_t=)
+ - [`mcpc_rsc_new`](#=mcpc_rsc_new=)
+ - [`mcpc_rsc_new2`](#=mcpc_rsc_new2=)
+ - [`mcpc_rsc_new3`](#=mcpc_rsc_new3=)
+ - [`mcpc_rsc_free`](#=mcpc_rsc_free=)
+ - [`mcpc_rsc_get_uri`](#=mcpc_rsc_get_uri=)
+ - [`mcpc_rsc_getref_uri`](#=mcpc_rsc_getref_uri=)
+ - [`mcpc_rsc_getref_name`](#=mcpc_rsc_getref_name=)
+ - [`mcpc_rsc_get_mime`](#=mcpc_rsc_get_mime=)
+- [Prompt](#org211140d)
+ - [`mcpc_prmpt_t`](#=mcpc_prmpt_t=)
+ - [`mcpc_prmptarg_t`](#=mcpc_prmptarg_t=)
+ - [`mcpc_prmptarghint_t`](#=mcpc_prmptarghint_t=)
+ - [`mcpc_prmpt_callcb_t`](#=mcpc_prmpt_callcb_t=)
+ - [`mcpc_prmptarg_new`](#=mcpc_prmptarg_new=)
+ - [`mcpc_prmptarg_new2`](#=mcpc_prmptarg_new2=)
+ - [`mcpc_prmptarg_new3`](#=mcpc_prmptarg_new3=)
+ - [`mcpc_prmpt_new`](#=mcpc_prmpt_new=)
+ - [`mcpc_prmpt_new2`](#=mcpc_prmpt_new2=)
+ - [`mcpc_prmpt_new3`](#=mcpc_prmpt_new3=)
+ - [`mcpc_prmpt_addfre_prmptarg`](#=mcpc_prmpt_addfre_prmptarg=)
+ - [`mcpc_prmpt_addcpy_prmptarg`](#=mcpc_prmpt_addcpy_prmptarg=)
+ - [`mcpc_prmpt_add_prmptarg`](#=mcpc_prmpt_add_prmptarg=)
+ - [`mcpc_prmpt_get_prmptarg` (deprecated)](#=mcpc_prmpt_get_prmptarg=)
+ - [`mcpc_prmpt_get_prmptarg_len`](#=mcpc_prmpt_get_prmptarg_len=)
+ - [`mcpc_prmpt_get_next_prmptarg`](#=mcpc_prmpt_get_next_prmptarg=)
+ - [`mcpc_prmpt_getbyname_prmptarg`](#=mcpc_prmpt_getbyname_prmptarg=)
+ - [`mcpc_prmpt_getbynament_prmptarg`](#=mcpc_prmpt_getbynament_prmptarg=)
+ - [`mcpc_prmpt_get_callcb`](#=mcpc_prmpt_get_callcb=)
+ - [`mcpc_prmpt_set_callcb`](#=mcpc_prmpt_set_callcb=)
+ - [`mcpc_prmptarg_get_u8str`](#=mcpc_prmptarg_get_u8str=)
+ - [`mcpc_prmptarg_getref_u8str`](#=mcpc_prmptarg_getref_u8str=)
+ - [`mcpc_prmptarg_add_hint_printf8`](#=mcpc_prmptarg_add_hint_printf8=)
+ - [`mcpc_prmptget_result_add_user_printf8`](#=mcpc_prmptget_result_add_user_printf8=)
+ - [`mcpc_prmptget_result_add_assist_printf8`](#=mcpc_prmptget_result_add_assist_printf8=)
+- [Others](#org6f84d9e)
+ - [`mcpc_errcode_t`](#=mcpc_)
+ - [`mcpc_anytype_t`](#=mcpc_anytype_t=)
+ - [`mcpc_mime_t`](#=mcpc_mime_t=)
+ - [`mcpc_ucbr_t`](#=mcpc_ucbr_t=)
+ - [`mcpc_ucbr_toolcall_add_text_printf8`](#=mcpc_ucbr_toolcall_add_text_printf8=)
+ - [`mcpc_ucbr_toolcall_add_errmsg_printf8`](#=mcpc_ucbr_toolcall_add_errmsg_printf8=)
+ - [`mcpc_ucbr_prmptget_add_user_printf8`](#=mcpc_ucbr_prmptget_add_user_printf8=)
+ - [`mcpc_ucbr_prmptget_add_assist_printf8`](#=mcpc_ucbr_prmptget_add_assist_printf8=)
+
+
+
+<a id="server"></a>
+
+# Server
+
+
+<a id="=mcpc_server_t="></a>
+
+## `mcpc_server_t`
+
+Server instance
+
+
+<a id="=mcpc_server_new_iostrm="></a>
+
+## `mcpc_server_new_iostrm`
+
+Create a I/O streams-base server.
+
+ mcpc_server_t *
+ mcpc_server_new_iostrm (const FILE *const strm_in, const FILE *const strm_out);
+
+`strm_in`: input stream
+
+`strm_out`: output stream
+
+
+<a id="=mcpc_server_new_tcp="></a>
+
+## `mcpc_server_new_tcp`
+
+Create a TCP-based server.
+
+ mcpc_server_t*
+ mcpc_server_new_tcp ();
+
+
+<a id="=mcpc_server_set_nament="></a>
+
+## `mcpc_server_set_nament`
+
+Set server's name.
+
+ mcpc_errcode_t
+ mcpc_server_set_nament (mcpc_server_t *sv, const char8_t *nament);
+
+`sv`: server
+
+`nament`: null-terminated name. Must be a UTF-8 string
+
+
+<a id="=mcpc_server_capa_enable_prmt="></a>
+
+## `mcpc_server_capa_enable_prmt`
+
+Enable server's general capability of *Prompt*.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_prmt (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_capa_enable_prmt_listchg="></a>
+
+## `mcpc_server_capa_enable_prmt_listchg`
+
+Enable server's capability of notification for Prompt list updates.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_prmt_listchg (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_capa_enable_tool="></a>
+
+## `mcpc_server_capa_enable_tool`
+
+Enable server's general capability of *Prompt*.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_tool (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_capa_enable_tool_listchg="></a>
+
+## `mcpc_server_capa_enable_tool_listchg`
+
+Enable server's capability of notification for Prompt list updates.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_tool_listchg (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_capa_enable_rsc="></a>
+
+## `mcpc_server_capa_enable_rsc`
+
+Enable server's general capability of *Resource*.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_rsc (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_capa_enable_rsc_subscr="></a>
+
+## `mcpc_server_capa_enable_rsc_subscr`
+
+Enable server's capability of notification for Resource subscription.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_rsc_subscr (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_capa_enable_rsc_listchg="></a>
+
+## `mcpc_server_capa_enable_rsc_listchg`
+
+Enable server's capability of notification for Prompt list updates.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_rsc_listchg (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_capa_enable_complt="></a>
+
+## `mcpc_server_capa_enable_complt`
+
+Enable server's general capability of *Completion*.
+
+ mcpc_errcode_t
+ mcpc_server_capa_enable_complt (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_add_tool="></a>
+
+## `mcpc_server_add_tool`
+
+Add a *Tool* to a server.
+
+ mcpc_errcode_t
+ mcpc_server_add_tool (mcpc_server_t *sv, mcpc_tool_t *tool);
+
+`sv`: server
+
+`tool`: tool, it will be automatically freed
+
+
+<a id="=mcpc_server_add_prmpt="></a>
+
+## `mcpc_server_add_prmpt`
+
+Add a *Prompt* to a server.
+
+ mcpc_errcode_t
+ mcpc_server_add_prmpt (mcpc_server_t *sv, mcpc_prmpt_t *prmpt);
+
+`sv`: server
+
+`prmpt`: prompt, it will be automatically freed
+
+
+<a id="=mcpc_server_add_rsc="></a>
+
+## `mcpc_server_add_rsc`
+
+Add a *Resource* to a server.
+
+ mcpc_errcode_t
+ mcpc_server_add_rsc (mcpc_server_t *sv, mcpc_rsc_t *rsc);
+
+`sv`: server
+
+`prmpt`: prompt, it will be automatically freed
+
+
+<a id="=mcpc_server_start="></a>
+
+## `mcpc_server_start`
+
+Start a server.
+
+ mcpc_errcode_t
+ mcpc_server_start (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="=mcpc_server_close="></a>
+
+## `mcpc_server_close`
+
+Close a server.
+
+ mcpc_errcode_t
+ mcpc_server_close (mcpc_server_t *sv);
+
+`sv`: server
+
+
+<a id="org386a1c2"></a>
+
+# Tool
+
+
+<a id="=mcpc_tool_t="></a>
+
+## `mcpc_tool_t`
+
+Tool
+
+
+<a id="=mcpc_toolprop_t="></a>
+
+## `mcpc_toolprop_t`
+
+Tool property
+
+
+<a id="=mcpc_tcallcb_t="></a>
+
+## `mcpc_tcallcb_t`
+
+The callback function of while doing tool calling.
+
+
+<a id="=mcpc_toolprop_new="></a>
+
+## `mcpc_toolprop_new`
+
+ mcpc_toolprop_t *
+ mcpc_toolprop_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len, mcpc_anytype_t typ);
+
+`name`: *tool* property's name
+
+`name_len`: the length of *tool* property's name
+
+`desc`: *tool* property's description
+
+`desc_len`: the length of *tool* property's description
+
+
+<a id="=mcpc_toolprop_new2="></a>
+
+## `mcpc_toolprop_new2`
+
+ mcpc_toolprop_t *
+ mcpc_toolprop_new2 (const char8_t *nament, const char8_t *descnt, mcpc_anytype_t typ);
+
+`nament`: *tool* property's name, must be null-terminated
+
+`descnt`: *tool* property's description, must be null-terminated
+
+## `mcpc_toolprop_is_required`
+
+ bool
+ mcpc_toolprop_is_required (const mcpc_toolprop_t *toolprop);
+
+Returns whether the property is listed in the tool input schema's `required` array.
+
+## `mcpc_toolprop_set_required`
+
+ mcpc_errcode_t
+ mcpc_toolprop_set_required (mcpc_toolprop_t *toolprop, bool required);
+
+Sets the property requirement used by both schema serialization and request handling.
+
+
+<a id="=mcpc_tool_new="></a>
+
+## `mcpc_tool_new`
+
+ mcpc_tool_t *
+ mcpc_tool_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+
+`name`: *tool*'s name
+
+`name_len`: the length of *tool*'s name
+
+`desc`: *tool*'s description
+
+`desc_len`: the length of *tool*'s description
+
+
+<a id="=mcpc_tool_new2="></a>
+
+## `mcpc_tool_new2`
+
+ mcpc_tool_t *
+ mcpc_tool_new2 (const char8_t *nament, const char8_t *descnt);
+
+`nament`: *tool*'s name, must be null-terminated
+
+`descnt`: *tool*'s description, must be null-terminated
+
+
+<a id="=mcpc_tool_addfre_toolprop="></a>
+
+## `mcpc_tool_addfre_toolprop`
+
+ void
+ mcpc_tool_addfre_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop);
+
+`tool`: *tool*
+
+`toolprop`: *tool* property
+
+
+<a id="=mcpc_tool_addcpy_toolprop="></a>
+
+## `mcpc_tool_addcpy_toolprop`
+
+ void
+ mcpc_tool_addcpy_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop);
+
+`tool`: *tool*
+
+`toolprop`: *tool* property
+
+
+<a id="=mcpc_tool_add_toolprop="></a>
+
+## `mcpc_tool_add_toolprop`
+
+Alias of `mcpc_tool_addfre_toolprop`
+
+
+<a id="=mcpc_tool_get_toolprop="></a>
+
+## `mcpc_tool_get_toolprop` (deprecate)
+
+ const mcpc_toolprop_t *
+ mcpc_tool_get_toolprop (const mcpc_tool_t *tool, size_t idx);
+
+`tool`: *tool*
+
+
+<a id="=mcpc_tool_get_toolprop_len="></a>
+
+## `mcpc_tool_get_toolprop_len`
+
+ size_t
+ mcpc_tool_get_toolprop_len (const mcpc_tool_t *tool);
+
+`tool`: *tool*
+
+
+<a id="=mcpc_tool_get_tpropval_i32="></a>
+
+## `mcpc_tool_get_tpropval_i32`
+
+ mcpc_errcode_t
+ mcpc_tool_get_tpropval_i32 (const mcpc_tool_t *tool, const char8_t *tprop_nament, int32_t *ret);
+
+`tool`: *tool*
+
+`tprop_nament`: *tool* property's name, null-terminated
+
+`ret`: the value of *tool* property
+
+
+<a id="=mcpc_tool_get_tpropval_u8str="></a>
+
+## `mcpc_tool_get_tpropval_u8str`
+
+ mcpc_errcode_t
+ mcpc_tool_get_tpropval_u8str (const mcpc_tool_t *tool, const char8_t *tprop_nament, char8_t *ret, size_t ret_cap, size_t *ret_len);
+
+`tool`: *tool*
+
+`tprop_nament`: *tool* property's name, null-terminated
+
+`ret`: the value of *tool* property
+
+
+<a id="=mcpc_tool_get_call_cb="></a>
+
+## `mcpc_tool_get_call_cb`
+
+ mcpc_errcode_t
+ mcpc_tool_get_call_cb (const mcpc_tool_t *tool, mcpc_tcallcb_t *ret);
+
+`tool`: *tool*
+
+`ret`: the callback for *tools/call*
+
+
+<a id="=mcpc_tool_set_call_cb="></a>
+
+## `mcpc_tool_set_call_cb`
+
+ mcpc_errcode_t
+ mcpc_tool_set_call_cb (mcpc_tool_t *tool, mcpc_tcallcb_t cb);
+
+`tool`: *tool*
+
+`ret`: the callback for *tools/call*
+
+
+<a id="=mcpc_toolcall_result_add_text_printf8="></a>
+
+## `mcpc_toolcall_result_add_text_printf8`
+
+Add a text content to the result of *tools/call*.
+
+Alias of `mcpc_ucbr_toolcall_add_text_printf8`.
+
+
+<a id="=mcpc_toolcall_result_add_errmsg_printf8="></a>
+
+## `mcpc_toolcall_result_add_errmsg_printf8`
+
+Add a error message to the result of *tools/call*. This will mark the
+whole result as an error.
+
+Alias of `mcpc_toolcall_result_add_errmsg_printf8`.
+
+
+<a id="org52ee5a7"></a>
+
+# Resource
+
+
+<a id="=mcpc_rsc_t="></a>
+
+## `mcpc_rsc_t`
+
+Resource
+
+
+<a id="=mcpc_rsc_new="></a>
+
+## `mcpc_rsc_new`
+
+ mcpc_rsc_t *
+ mcpc_rsc_new (const char8_t *uri, const size_t uri_len, const char8_t *name, const size_t name_len);
+
+`uri`: *Resource*'s URI
+
+`uri_len`: the length of *Resource*'s URI
+
+`name`: *Resource*'s name
+
+`name_len`: the length of *Resource*'s name
+
+
+<a id="=mcpc_rsc_new2="></a>
+
+## `mcpc_rsc_new2`
+
+ mcpc_rsc_t *
+ mcpc_rsc_new2 (const char8_t *uri_nt, const char8_t *nament);
+
+`uri_nt`: *Resource*'s URI, must be null-terminated
+
+`nament`: *Resource*'s name, must be null-terminated
+
+
+<a id="=mcpc_rsc_new3="></a>
+
+## `mcpc_rsc_new3`
+
+ mcpc_rsc_t *
+ mcpc_rsc_new3 (mcpc_mime_t mime, const char8_t *uri_nt, const char8_t *nament);
+
+`mime`: *Resource*'s MIME type
+
+`uri_nt`: *Resource*'s URI, must be null-terminated
+
+`nament`: *Resource*'s name, must be null-terminated
+
+
+<a id="=mcpc_rsc_free="></a>
+
+## `mcpc_rsc_free`
+
+ void
+ mcpc_rsc_free (mcpc_rsc_t *rsc);
+
+`rsc`: *Resource*
+
+
+<a id="=mcpc_rsc_get_uri="></a>
+
+## `mcpc_rsc_get_uri`
+
+ mcpc_errcode_t
+ mcpc_rsc_get_uri (const mcpc_rsc_t *rsc, char8_t *ret, size_t ret_cap, size_t *ret_len);
+
+`rsc`: *Resource*
+
+
+<a id="=mcpc_rsc_getref_uri="></a>
+
+## `mcpc_rsc_getref_uri`
+
+ mcpc_errcode_t
+ mcpc_rsc_getref_uri (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len);
+
+`rsc`: *Resource*.
+
+`ret`: the pointer to *Resource*'s URI.
+
+`ret_len`: the length of *Resource*'s URI.
+
+
+<a id="=mcpc_rsc_getref_name="></a>
+
+## `mcpc_rsc_getref_name`
+
+ mcpc_errcode_t
+ mcpc_rsc_getref_name (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len);
+
+`rsc`: *Resource*.
+
+`ret`: the pointer to *Resource*'s name.
+
+`ret_len`: the length of *Resource*'s name.
+
+
+<a id="=mcpc_rsc_get_mime="></a>
+
+## `mcpc_rsc_get_mime`
+
+ mcpc_errcode_t
+ mcpc_rsc_get_mime (const mcpc_rsc_t *rsc, mcpc_mime_t *ret);
+
+`rsc`: *Resource*.
+
+`ret`: *Resource*'s MIME type.
+
+
+<a id="org211140d"></a>
+
+# Prompt
+
+
+<a id="=mcpc_prmpt_t="></a>
+
+## `mcpc_prmpt_t`
+
+Prompt
+
+
+<a id="=mcpc_prmptarg_t="></a>
+
+## `mcpc_prmptarg_t`
+
+prompt argument
+
+
+<a id="=mcpc_prmptarghint_t="></a>
+
+## `mcpc_prmptarghint_t`
+
+prompt argument hint
+
+
+<a id="=mcpc_prmpt_callcb_t="></a>
+
+## `mcpc_prmpt_callcb_t`
+
+callback for prompts/get
+
+
+<a id="=mcpc_prmptarg_new="></a>
+
+## `mcpc_prmptarg_new`
+
+ mcpc_prmptarg_t *
+ mcpc_prmptarg_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+
+`name`: *Prompt* argument's name
+
+`name_len`: the length of *Prompt* argument's name
+
+`desc`: *Prompt* argument's description
+
+`desc_len`: the length of *Prompt* argument's description
+
+
+<a id="=mcpc_prmptarg_new2="></a>
+
+## `mcpc_prmptarg_new2`
+
+ mcpc_prmptarg_t *
+ mcpc_prmptarg_new2 (const char8_t *nament, const char8_t *descnt);
+
+`nament`: *Prompt* argument's name, must be null-terminated
+
+`descnt`: *Prompt* argument's description, must be null-terminated
+
+
+<a id="=mcpc_prmptarg_new3="></a>
+
+## `mcpc_prmptarg_new3`
+
+ mcpc_prmptarg_t *
+ mcpc_prmptarg_new3 (const char8_t *nament);
+
+`nament`: *Prompt* argument's name, must be null-terminated
+
+
+<a id="=mcpc_prmpt_new="></a>
+
+## `mcpc_prmpt_new`
+
+ mcpc_prmpt_t *
+ mcpc_prmpt_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+
+`name`: *Prompt*'s name
+
+`name_len`: the length of *Prompt*'s name
+
+`desc`: *Prompt*'s description
+
+`desc_len`: the length of *Prompt*'s description
+
+
+<a id="=mcpc_prmpt_new2="></a>
+
+## `mcpc_prmpt_new2`
+
+ mcpc_prmpt_t *
+ mcpc_prmpt_new2 (const char8_t *nament, const char8_t *descnt);
+
+`nament`: *Prompt*'s name, must be null-terminated
+
+`descnt`: *Prompt*'s description, must be null-terminated
+
+
+<a id="=mcpc_prmpt_new3="></a>
+
+## `mcpc_prmpt_new3`
+
+ mcpc_prmpt_t *
+ mcpc_prmpt_new3 (const char8_t *nament);
+
+`nament`: *Prompt*'s name, must be null-terminated
+
+
+<a id="=mcpc_prmpt_addfre_prmptarg="></a>
+
+## `mcpc_prmpt_addfre_prmptarg`
+
+ mcpc_errcode_t
+ mcpc_prmpt_addfre_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg);
+
+`prmpt`: *Prompt*
+
+`prmptarg`: *Prompt* argument
+
+
+<a id="=mcpc_prmpt_addcpy_prmptarg="></a>
+
+## `mcpc_prmpt_addcpy_prmptarg`
+
+ void
+ mcpc_prmpt_addcpy_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg);
+
+`prmpt`: *Prompt*
+
+`prmptarg`: *Prompt* argument
+
+
+<a id="=mcpc_prmpt_add_prmptarg="></a>
+
+## `mcpc_prmpt_add_prmptarg`
+
+Alias of `mcpc_prmpt_addfre_prmptarg`.
+
+
+<a id="=mcpc_prmpt_get_prmptarg="></a>
+
+## `mcpc_prmpt_get_prmptarg` (deprecated)
+
+ const mcpc_prmptarg_t *
+ mcpc_prmpt_get_prmptarg (const mcpc_prmpt_t *prmpt, size_t idx);
+
+`prmpt`: *Prompt*
+
+`idx`: &#x2026;
+
+
+<a id="=mcpc_prmpt_get_prmptarg_len="></a>
+
+## `mcpc_prmpt_get_prmptarg_len`
+
+ size_t
+ mcpc_prmpt_get_prmptarg_len (const mcpc_prmpt_t *prmpt);
+
+`prmpt`: *Prompt*
+
+
+<a id="=mcpc_prmpt_get_next_prmptarg="></a>
+
+## `mcpc_prmpt_get_next_prmptarg`
+
+ const mcpc_prmptarg_t *
+ mcpc_prmpt_get_next_prmptarg (const mcpc_prmpt_t *prmpt);
+
+`prmpt`: *Prompt*
+
+
+<a id="=mcpc_prmpt_getbyname_prmptarg="></a>
+
+## `mcpc_prmpt_getbyname_prmptarg`
+
+ const mcpc_prmptarg_t *
+ mcpc_prmpt_getbyname_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_name, size_t parg_name_len);
+
+`prmpt`: *Prompt*
+
+`parg_name`: *Prompt* argument's name
+
+`parg_name_len`: the length of *Prompt* argument's name
+
+
+<a id="=mcpc_prmpt_getbynament_prmptarg="></a>
+
+## `mcpc_prmpt_getbynament_prmptarg`
+
+ const mcpc_prmptarg_t *
+ mcpc_prmpt_getbynament_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_nament);
+
+`prmpt`: *Prompt*
+
+`parg_nament`: *Prompt* argument's name, must be null-terminated
+
+
+<a id="=mcpc_prmpt_get_callcb="></a>
+
+## `mcpc_prmpt_get_callcb`
+
+ mcpc_errcode_t
+ mcpc_prmpt_get_callcb (const mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t *ret);
+
+`prmpt`: *Prompt*
+
+`ret`: the callback for *prompts/get*
+
+
+<a id="=mcpc_prmpt_set_callcb="></a>
+
+## `mcpc_prmpt_set_callcb`
+
+ mcpc_errcode_t
+ mcpc_prmpt_set_callcb (mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t cb);
+
+`prmpt`: *prompt*
+
+`ret`: the callback for *prompts/get*
+
+
+<a id="=mcpc_prmptarg_get_u8str="></a>
+
+## `mcpc_prmptarg_get_u8str`
+
+ mcpc_errcode_t
+ mcpc_prmptarg_get_u8str (const mcpc_prmptarg_t *prmptarg, char8_t *ret, size_t ret_cap, size_t *ret_len);
+
+`prmptarg`: *prompt* argument
+
+`ret`: caller-provided buffer
+
+`ret_cap`: the buffer's capacity
+
+`ret_len`: the buffer's length
+
+
+<a id="=mcpc_prmptarg_getref_u8str="></a>
+
+## `mcpc_prmptarg_getref_u8str`
+
+ mcpc_errcode_t
+ mcpc_prmptarg_getref_u8str (const mcpc_prmptarg_t *prmptarg, const char8_t **ret, size_t *ret_len);
+
+`prmptarg`: *prompt* argument
+
+`ret`: the pointer to prompt's argument string data
+
+`ret_len`: the length of prompt's argument string data
+
+
+<a id="=mcpc_prmptarg_add_hint_printf8="></a>
+
+## `mcpc_prmptarg_add_hint_printf8`
+
+ mcpc_errcode_t
+ mcpc_prmptarg_add_hint_printf8 (mcpc_prmptarg_t *prmptarg, const char8_t *fmt, ...);
+
+`prmptarg`: *prompt* argument
+
+`fmt`: the format string to print
+
+
+<a id="=mcpc_prmptget_result_add_user_printf8="></a>
+
+## `mcpc_prmptget_result_add_user_printf8`
+
+Add a complete <span class="underline">user</span> prompt to the result of *prompts/get*.
+
+Alias of `mcpc_ucbr_prmptget_add_user_printf8`.
+
+
+<a id="=mcpc_prmptget_result_add_assist_printf8="></a>
+
+## `mcpc_prmptget_result_add_assist_printf8`
+
+Add a complete <span class="underline">assistant</span> prompt to the result of *prompts/get*.
+
+Alias of `mcpc_ucbr_prmptget_add_assist_printf8`.
+
+
+<a id="org6f84d9e"></a>
+
+# Others
+
+
+<a id="=mcpc_"></a>
+
+## `mcpc_errcode_t`
+
+Library-specific error codes.
+
+
+### Possible values
+
+`MCPC_EC_0`: success
+
+`MCPC_EC_BUG`: bug
+
+`MCPC_EC_BUFCAP`: insufficient buffer capacity
+
+`MCPC_EC_LONGTOOLNAME`: tool's name is too long
+
+`MCPC_EC_LONGTOOLDESC`: tool's description is too long
+
+`MCPC_EC_LONGTPROPNAME`: tool property's name is too long
+
+`MCPC_EC_LONGTPROPDESC`: tool property's description is too long
+
+`MCPC_EC_TOOLPROP_NOTFOUND`: tool property is not found
+
+`MCPC_EC_TOOLCALL_USERERR`: after tool calling, user indicates an error
+
+`MCPC_EC_CONNPOOL_NOTADD`: connection cannot be added
+
+`MCPC_EC_SVIMPL_SOCKET`: fail to create TCP socket
+
+`MCPC_EC_SVIMPL_BIND`: fail to bind TCP socket
+
+`MCPC_EC_SVIMPL_LISTEN`: fail to listen TCP socket
+
+`MCPC_EC_SVIMPL_TCP_INSUF_BUF`: insufficient buffer capacity
+
+`MCPC_EC_ANYDATA_NOTFOUND`: data cannot be found
+
+`MCPC_EC_ANYDATA_LONGNAME`: data's name is too long
+
+`MCPC_EC_PRMPTARG_NOTFOUND`: prompt agument cannot be found
+
+`MCPC_EC_JSON_FIELD_INVALID`: json field's value invalid, this means
+the field is missing, or field has unexpected type
+
+
+<a id="=mcpc_anytype_t="></a>
+
+## `mcpc_anytype_t`
+
+The data type recognizable by the library.
+
+
+### Possible values
+
+`MCPC_NONE`: nothing
+
+`MCPC_I8`: `int8_t`-like type
+
+`MCPC_U8`: `uint8_t`-like type
+
+`MCPC_I16`: `int16_t`-like type
+
+`MCPC_U16`: `uint16_t`-like type
+
+`MCPC_I32`: `int32_t`-like type
+
+`MCPC_U32`: `uint32_t`-like type
+
+`MCPC_I64`: `int64_t`-like type
+
+`MCPC_U64`: `uint64_t`-like type
+
+`MCPC_U8STR`: string of UTF-8 code units
+
+`MCPC_I8S`: an array of `int8_t`
+
+`MCPC_U8S`: an array of `uint8_t`
+
+`MCPC_I16S`: an array of `int16_t`
+
+`MCPC_U16S`: an array of `uint16_t`
+
+`MCPC_I32S`: an array of `int32_t`
+
+`MCPC_U32S`: an array of `uint32_t`
+
+`MCPC_I64S`: an array of `int64_t`
+
+`MCPC_U64S`: an array of `uint64_t`
+
+`MCPC_FUNC`: a function
+
+
+<a id="=mcpc_mime_t="></a>
+
+## `mcpc_mime_t`
+
+MIME types recognizable by the library, that conforms to [RFC2045](https://www.rfc-editor.org/rfc/rfc2045).
+
+
+### Possible value
+
+`MCPC_MIME_NONE`: nothing
+
+`MCPC_MIME_TEXT_PLAIN`: text/plain
+
+`MCPC_MIME_IMAGE_PNG`: image/png
+
+`MCPC_MIME_IMAGE_JPEG`: image/jpeg
+
+`MCPC_MIME_IMAGE_GIF`: image/gif
+
+`MCPC_MIME_AUDIO_MPEG`: audio/mpeg
+
+`MCPC_MIME_AUDIO_WAV`: audio/wav
+
+`MCPC_MIME_VIDEO_MPEG`: video/mpeg
+
+`MCPC_MIME_VIDEO_AVI`: video/avi
+
+`MCPC_MIME_APPLI_OCTS`: application/octet-stream
+
+`MCPC_MIME_APPLI_JSON`: application/json
+
+`MCPC_MIME_APPLI_PDF`: application/pdf
+
+
+<a id="=mcpc_ucbr_t="></a>
+
+## `mcpc_ucbr_t`
+
+User call back result.
+
+
+<a id="=mcpc_ucbr_toolcall_add_text_printf8="></a>
+
+## `mcpc_ucbr_toolcall_add_text_printf8`
+
+Add a text message to the result of *tools/call*.
+
+ void
+ mcpc_ucbr_toolcall_add_text_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+`ucbr`: the callback result
+
+`fmt`: format string to be printed
+
+
+<a id="=mcpc_ucbr_toolcall_add_errmsg_printf8="></a>
+
+## `mcpc_ucbr_toolcall_add_errmsg_printf8`
+
+Add a error message to the result of *tools/call*.
+
+ void
+ mcpc_ucbr_toolcall_add_errmsg_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+`ucbr`: the callback result
+
+`fmt`: format string to be printed
+
+
+<a id="=mcpc_ucbr_prmptget_add_user_printf8="></a>
+
+## `mcpc_ucbr_prmptget_add_user_printf8`
+
+Add a user prompt to the result of *prompts/get*.
+
+ void
+ mcpc_ucbr_prmptget_add_user_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+`ucbr`: the callback result
+
+`fmt`: format string to be printed
+
+
+<a id="=mcpc_ucbr_prmptget_add_assist_printf8="></a>
+
+## `mcpc_ucbr_prmptget_add_assist_printf8`
+
+Add an assistant prompt to the result of *prompts/get*.
+
+ void
+ mcpc_ucbr_prmptget_add_assist_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+
+`ucbr`: the callback result
+
+`fmt`: format string to be printed
+
diff --git a/third_party/mcpc/misc/api.org b/third_party/mcpc/misc/api.org
new file mode 100644
index 0000000..c3b33e5
--- /dev/null
+++ b/third_party/mcpc/misc/api.org
@@ -0,0 +1,1193 @@
+#+TITLE: API reference for mcpc
+#+OPTIONS: num:nil toc:2
+#+HTML_HEAD: <style> .outline-3 { border-top: 2px solid lightgray; } </style>
+
+
+* Server
+:PROPERTIES:
+:CUSTOM_ID: server
+:END:
+
+** =mcpc_server_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_t=
+:END:
+
+Server instance
+
+
+** =mcpc_server_new_iostrm=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_new_iostrm=
+:END:
+
+Create a I/O streams-base server.
+
+#+begin_src
+mcpc_server_t *
+mcpc_server_new_iostrm (const FILE *const strm_in, const FILE *const strm_out);
+#+end_src
+
+=strm_in=: input stream
+
+=strm_out=: output stream
+
+
+** =mcpc_server_new_tcp=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_new_tcp=
+:END:
+
+Create a TCP-based server.
+
+#+begin_src
+mcpc_server_t*
+mcpc_server_new_tcp ();
+#+end_src
+
+
+** =mcpc_server_set_nament=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_set_nament=
+:END:
+
+Set server's name.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_set_nament (mcpc_server_t *sv, const char8_t *nament);
+#+end_src
+
+=sv=: server
+
+=nament=: null-terminated name. Must be a UTF-8 string
+
+
+** =mcpc_server_capa_enable_prmt=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_prmt=
+:END:
+
+Enable server's general capability of /Prompt/.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_prmt (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_capa_enable_prmt_listchg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_prmt_listchg=
+:END:
+
+Enable server's capability of notification for Prompt list updates.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_prmt_listchg (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_capa_enable_tool=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_tool=
+:END:
+
+Enable server's general capability of /Prompt/.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_tool (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_capa_enable_tool_listchg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_tool_listchg=
+:END:
+
+Enable server's capability of notification for Prompt list updates.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_tool_listchg (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_capa_enable_rsc=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_rsc=
+:END:
+
+Enable server's general capability of /Resource/.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_rsc (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_capa_enable_rsc_subscr=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_rsc_subscr=
+:END:
+
+Enable server's capability of notification for Resource subscription.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_rsc_subscr (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_capa_enable_rsc_listchg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_rsc_listchg=
+:END:
+
+Enable server's capability of notification for Prompt list updates.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_rsc_listchg (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_capa_enable_complt=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_capa_enable_complt=
+:END:
+
+Enable server's general capability of /Completion/.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_capa_enable_complt (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_add_tool=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_add_tool=
+:END:
+
+Add a /Tool/ to a server.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_add_tool (mcpc_server_t *sv, mcpc_tool_t *tool);
+#+end_src
+
+=sv=: server
+
+=tool=: tool, it will be automatically freed
+
+
+** =mcpc_server_add_prmpt=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_add_prmpt=
+:END:
+
+Add a /Prompt/ to a server.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_add_prmpt (mcpc_server_t *sv, mcpc_prmpt_t *prmpt);
+#+end_src
+
+=sv=: server
+
+=prmpt=: prompt, it will be automatically freed
+
+
+** =mcpc_server_add_rsc=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_add_rsc=
+:END:
+
+Add a /Resource/ to a server.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_add_rsc (mcpc_server_t *sv, mcpc_rsc_t *rsc);
+#+end_src
+
+=sv=: server
+
+=prmpt=: prompt, it will be automatically freed
+
+
+** =mcpc_server_start=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_start=
+:END:
+
+Start a server.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_start (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+** =mcpc_server_close=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_server_close=
+:END:
+
+Close a server.
+
+#+begin_src
+mcpc_errcode_t
+mcpc_server_close (mcpc_server_t *sv);
+#+end_src
+
+=sv=: server
+
+
+* Tool
+
+** =mcpc_tool_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_t=
+:END:
+
+Tool
+
+
+** =mcpc_toolprop_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_toolprop_t=
+:END:
+
+Tool property
+
+
+** =mcpc_tcallcb_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tcallcb_t=
+:END:
+
+The callback function of while doing tool calling.
+
+
+** =mcpc_toolprop_new=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_toolprop_new=
+:END:
+
+#+begin_src
+mcpc_toolprop_t *
+mcpc_toolprop_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len, mcpc_anytype_t typ);
+#+end_src
+
+=name=: /tool/ property's name
+
+=name_len=: the length of /tool/ property's name
+
+=desc=: /tool/ property's description
+
+=desc_len=: the length of /tool/ property's description
+
+
+** =mcpc_toolprop_new2=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_toolprop_new2=
+:END:
+
+#+begin_src
+mcpc_toolprop_t *
+mcpc_toolprop_new2 (const char8_t *nament, const char8_t *descnt, mcpc_anytype_t typ);
+#+end_src
+
+=nament=: /tool/ property's name, must be null-terminated
+
+=descnt=: /tool/ property's description, must be null-terminated
+
+
+** =mcpc_tool_new=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_new=
+:END:
+
+#+begin_src
+mcpc_tool_t *
+mcpc_tool_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+#+end_src
+
+=name=: /tool/'s name
+
+=name_len=: the length of /tool/'s name
+
+=desc=: /tool/'s description
+
+=desc_len=: the length of /tool/'s description
+
+
+** =mcpc_tool_new2=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_new2=
+:END:
+
+#+begin_src
+mcpc_tool_t *
+mcpc_tool_new2 (const char8_t *nament, const char8_t *descnt);
+#+end_src
+
+=nament=: /tool/'s name, must be null-terminated
+
+=descnt=: /tool/'s description, must be null-terminated
+
+
+
+** =mcpc_tool_addfre_toolprop=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_addfre_toolprop=
+:END:
+
+#+begin_src
+void
+mcpc_tool_addfre_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop);
+#+end_src
+
+=tool=: /tool/
+
+=toolprop=: /tool/ property
+
+
+** =mcpc_tool_addcpy_toolprop=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_addcpy_toolprop=
+:END:
+
+#+begin_src
+void
+mcpc_tool_addcpy_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop);
+#+end_src
+
+=tool=: /tool/
+
+=toolprop=: /tool/ property
+
+
+
+** =mcpc_tool_add_toolprop=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_add_toolprop=
+:END:
+
+Alias of =mcpc_tool_addfre_toolprop=
+
+
+** =mcpc_tool_get_toolprop= (deprecate)
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_get_toolprop=
+:END:
+
+#+begin_src
+const mcpc_toolprop_t *
+mcpc_tool_get_toolprop (const mcpc_tool_t *tool, size_t idx);
+#+end_src
+
+=tool=: /tool/
+
+
+** =mcpc_tool_get_toolprop_len=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_get_toolprop_len=
+:END:
+
+#+begin_src
+size_t
+mcpc_tool_get_toolprop_len (const mcpc_tool_t *tool);
+#+end_src
+
+=tool=: /tool/
+
+
+** =mcpc_tool_get_tpropval_i32=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_get_tpropval_i32=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_tool_get_tpropval_i32 (const mcpc_tool_t *tool, const char8_t *tprop_nament, int32_t *ret);
+#+end_src
+
+=tool=: /tool/
+
+=tprop_nament=: /tool/ property's name, null-terminated
+
+=ret=: the value of /tool/ property
+
+
+** =mcpc_tool_get_tpropval_u8str=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_get_tpropval_u8str=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_tool_get_tpropval_u8str (const mcpc_tool_t *tool, const char8_t *tprop_nament, char8_t *ret, size_t ret_cap, size_t *ret_len);
+#+end_src
+
+=tool=: /tool/
+
+=tprop_nament=: /tool/ property's name, null-terminated
+
+=ret=: the value of /tool/ property
+
+
+** =mcpc_tool_get_call_cb=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_get_call_cb=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_tool_get_call_cb (const mcpc_tool_t *tool, mcpc_tcallcb_t *ret);
+#+end_src
+
+=tool=: /tool/
+
+=ret=: the callback for /tools/call/
+
+
+** =mcpc_tool_set_call_cb=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_tool_set_call_cb=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_tool_set_call_cb (mcpc_tool_t *tool, mcpc_tcallcb_t cb);
+#+end_src
+
+=tool=: /tool/
+
+=ret=: the callback for /tools/call/
+
+
+** =mcpc_toolcall_result_add_text_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_toolcall_result_add_text_printf8=
+:END:
+
+Add a text content to the result of /tools/call/.
+
+Alias of =mcpc_ucbr_toolcall_add_text_printf8=.
+
+
+** =mcpc_toolcall_result_add_errmsg_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_toolcall_result_add_errmsg_printf8=
+:END:
+
+Add a error message to the result of /tools/call/. This will mark the
+whole result as an error.
+
+Alias of =mcpc_toolcall_result_add_errmsg_printf8=.
+
+
+* Resource
+
+** =mcpc_rsc_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_t=
+:END:
+
+Resource
+
+
+** =mcpc_rsc_new=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_new=
+:END:
+
+#+begin_src
+mcpc_rsc_t *
+mcpc_rsc_new (const char8_t *uri, const size_t uri_len, const char8_t *name, const size_t name_len);
+#+end_src
+
+=uri=: /Resource/'s URI
+
+=uri_len=: the length of /Resource/'s URI
+
+=name=: /Resource/'s name
+
+=name_len=: the length of /Resource/'s name
+
+
+** =mcpc_rsc_new2=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_new2=
+:END:
+
+#+begin_src
+mcpc_rsc_t *
+mcpc_rsc_new2 (const char8_t *uri_nt, const char8_t *nament);
+#+end_src
+
+=uri_nt=: /Resource/'s URI, must be null-terminated
+
+=nament=: /Resource/'s name, must be null-terminated
+
+
+** =mcpc_rsc_new3=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_new3=
+:END:
+
+#+begin_src
+mcpc_rsc_t *
+mcpc_rsc_new3 (mcpc_mime_t mime, const char8_t *uri_nt, const char8_t *nament);
+#+end_src
+
+=mime=: /Resource/'s MIME type
+
+=uri_nt=: /Resource/'s URI, must be null-terminated
+
+=nament=: /Resource/'s name, must be null-terminated
+
+
+** =mcpc_rsc_free=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_free=
+:END:
+
+#+begin_src
+void
+mcpc_rsc_free (mcpc_rsc_t *rsc);
+#+end_src
+
+=rsc=: /Resource/
+
+
+** =mcpc_rsc_get_uri=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_get_uri=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_rsc_get_uri (const mcpc_rsc_t *rsc, char8_t *ret, size_t ret_cap, size_t *ret_len);
+#+end_src
+
+=rsc=: /Resource/
+
+
+** =mcpc_rsc_getref_uri=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_getref_uri=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_rsc_getref_uri (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len);
+#+end_src
+
+=rsc=: /Resource/.
+
+=ret=: the pointer to /Resource/'s URI.
+
+=ret_len=: the length of /Resource/'s URI.
+
+
+** =mcpc_rsc_getref_name=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_getref_name=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_rsc_getref_name (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len);
+#+end_src
+
+=rsc=: /Resource/.
+
+=ret=: the pointer to /Resource/'s name.
+
+=ret_len=: the length of /Resource/'s name.
+
+
+** =mcpc_rsc_get_mime=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_rsc_get_mime=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_rsc_get_mime (const mcpc_rsc_t *rsc, mcpc_mime_t *ret);
+#+end_src
+
+=rsc=: /Resource/.
+
+=ret=: /Resource/'s MIME type.
+
+
+* Prompt
+
+** =mcpc_prmpt_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_t=
+:END:
+
+Prompt
+
+
+** =mcpc_prmptarg_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarg_t=
+:END:
+
+prompt argument
+
+
+** =mcpc_prmptarghint_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarghint_t=
+:END:
+
+prompt argument hint
+
+
+** =mcpc_prmpt_callcb_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_callcb_t=
+:END:
+
+callback for prompts/get
+
+
+** =mcpc_prmptarg_new=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarg_new=
+:END:
+
+#+begin_src
+mcpc_prmptarg_t *
+mcpc_prmptarg_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+#+end_src
+
+=name=: /Prompt/ argument's name
+
+=name_len=: the length of /Prompt/ argument's name
+
+=desc=: /Prompt/ argument's description
+
+=desc_len=: the length of /Prompt/ argument's description
+
+
+** =mcpc_prmptarg_new2=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarg_new2=
+:END:
+
+#+begin_src
+mcpc_prmptarg_t *
+mcpc_prmptarg_new2 (const char8_t *nament, const char8_t *descnt);
+#+end_src
+
+=nament=: /Prompt/ argument's name, must be null-terminated
+
+=descnt=: /Prompt/ argument's description, must be null-terminated
+
+
+** =mcpc_prmptarg_new3=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarg_new3=
+:END:
+
+#+begin_src
+mcpc_prmptarg_t *
+mcpc_prmptarg_new3 (const char8_t *nament);
+#+end_src
+
+=nament=: /Prompt/ argument's name, must be null-terminated
+
+
+** =mcpc_prmpt_new=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_new=
+:END:
+
+#+begin_src
+mcpc_prmpt_t *
+mcpc_prmpt_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len);
+#+end_src
+
+=name=: /Prompt/'s name
+
+=name_len=: the length of /Prompt/'s name
+
+=desc=: /Prompt/'s description
+
+=desc_len=: the length of /Prompt/'s description
+
+
+** =mcpc_prmpt_new2=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_new2=
+:END:
+
+#+begin_src
+mcpc_prmpt_t *
+mcpc_prmpt_new2 (const char8_t *nament, const char8_t *descnt);
+#+end_src
+
+=nament=: /Prompt/'s name, must be null-terminated
+
+=descnt=: /Prompt/'s description, must be null-terminated
+
+
+** =mcpc_prmpt_new3=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_new3=
+:END:
+
+#+begin_src
+mcpc_prmpt_t *
+mcpc_prmpt_new3 (const char8_t *nament);
+#+end_src
+
+=nament=: /Prompt/'s name, must be null-terminated
+
+
+** =mcpc_prmpt_addfre_prmptarg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_addfre_prmptarg=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_prmpt_addfre_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg);
+#+end_src
+
+=prmpt=: /Prompt/
+
+=prmptarg=: /Prompt/ argument
+
+
+** =mcpc_prmpt_addcpy_prmptarg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_addcpy_prmptarg=
+:END:
+
+#+begin_src
+void
+mcpc_prmpt_addcpy_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg);
+#+end_src
+
+=prmpt=: /Prompt/
+
+=prmptarg=: /Prompt/ argument
+
+
+** =mcpc_prmpt_add_prmptarg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_add_prmptarg=
+:END:
+
+Alias of =mcpc_prmpt_addfre_prmptarg=.
+
+** =mcpc_prmpt_get_prmptarg= (deprecated)
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_get_prmptarg=
+:END:
+
+#+begin_src
+const mcpc_prmptarg_t *
+mcpc_prmpt_get_prmptarg (const mcpc_prmpt_t *prmpt, size_t idx);
+#+end_src
+
+=prmpt=: /Prompt/
+
+=idx=: ...
+
+
+** =mcpc_prmpt_get_prmptarg_len=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_get_prmptarg_len=
+:END:
+
+#+begin_src
+size_t
+mcpc_prmpt_get_prmptarg_len (const mcpc_prmpt_t *prmpt);
+#+end_src
+
+=prmpt=: /Prompt/
+
+
+** =mcpc_prmpt_get_next_prmptarg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_get_next_prmptarg=
+:END:
+
+#+begin_src
+const mcpc_prmptarg_t *
+mcpc_prmpt_get_next_prmptarg (const mcpc_prmpt_t *prmpt);
+#+end_src
+
+=prmpt=: /Prompt/
+
+
+** =mcpc_prmpt_getbyname_prmptarg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_getbyname_prmptarg=
+:END:
+
+#+begin_src
+const mcpc_prmptarg_t *
+mcpc_prmpt_getbyname_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_name, size_t parg_name_len);
+#+end_src
+
+=prmpt=: /Prompt/
+
+=parg_name=: /Prompt/ argument's name
+
+=parg_name_len=: the length of /Prompt/ argument's name
+
+
+** =mcpc_prmpt_getbynament_prmptarg=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_getbynament_prmptarg=
+:END:
+
+#+begin_src
+const mcpc_prmptarg_t *
+mcpc_prmpt_getbynament_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_nament);
+#+end_src
+
+=prmpt=: /Prompt/
+
+=parg_nament=: /Prompt/ argument's name, must be null-terminated
+
+
+** =mcpc_prmpt_get_callcb=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_get_callcb=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_prmpt_get_callcb (const mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t *ret);
+#+end_src
+
+=prmpt=: /Prompt/
+
+=ret=: the callback for /prompts/get/
+
+
+** =mcpc_prmpt_set_callcb=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmpt_set_callcb=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_prmpt_set_callcb (mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t cb);
+#+end_src
+
+=prmpt=: /prompt/
+
+=ret=: the callback for /prompts/get/
+
+
+** =mcpc_prmptarg_get_u8str=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarg_get_u8str=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_prmptarg_get_u8str (const mcpc_prmptarg_t *prmptarg, char8_t *ret, size_t ret_cap, size_t *ret_len);
+#+end_src
+
+=prmptarg=: /prompt/ argument
+
+=ret=: caller-provided buffer
+
+=ret_cap=: the buffer's capacity
+
+=ret_len=: the buffer's length
+
+
+** =mcpc_prmptarg_getref_u8str=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarg_getref_u8str=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_prmptarg_getref_u8str (const mcpc_prmptarg_t *prmptarg, const char8_t **ret, size_t *ret_len);
+#+end_src
+
+=prmptarg=: /prompt/ argument
+
+=ret=: the pointer to prompt's argument string data
+
+=ret_len=: the length of prompt's argument string data
+
+
+** =mcpc_prmptarg_add_hint_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptarg_add_hint_printf8=
+:END:
+
+#+begin_src
+mcpc_errcode_t
+mcpc_prmptarg_add_hint_printf8 (mcpc_prmptarg_t *prmptarg, const char8_t *fmt, ...);
+#+end_src
+
+=prmptarg=: /prompt/ argument
+
+=fmt=: the format string to print
+
+
+** =mcpc_prmptget_result_add_user_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptget_result_add_user_printf8=
+:END:
+
+Add a complete _user_ prompt to the result of /prompts/get/.
+
+Alias of =mcpc_ucbr_prmptget_add_user_printf8=.
+
+
+** =mcpc_prmptget_result_add_assist_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_prmptget_result_add_assist_printf8=
+:END:
+
+
+Add a complete _assistant_ prompt to the result of /prompts/get/.
+
+Alias of =mcpc_ucbr_prmptget_add_assist_printf8=.
+
+
+* Others
+
+** =mcpc_errcode_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_
+:END:
+
+Library-specific error codes.
+
+*** Possible values
+
+=MCPC_EC_0=: success
+
+=MCPC_EC_BUG=: bug
+
+=MCPC_EC_BUFCAP=: insufficient buffer capacity
+
+=MCPC_EC_LONGTOOLNAME=: tool's name is too long
+
+=MCPC_EC_LONGTOOLDESC=: tool's description is too long
+
+=MCPC_EC_LONGTPROPNAME=: tool property's name is too long
+
+=MCPC_EC_LONGTPROPDESC=: tool property's description is too long
+
+=MCPC_EC_TOOLPROP_NOTFOUND=: tool property is not found
+
+=MCPC_EC_TOOLCALL_USERERR=: after tool calling, user indicates an error
+
+=MCPC_EC_CONNPOOL_NOTADD=: connection cannot be added
+
+=MCPC_EC_SVIMPL_SOCKET=: fail to create TCP socket
+
+=MCPC_EC_SVIMPL_BIND=: fail to bind TCP socket
+
+=MCPC_EC_SVIMPL_LISTEN=: fail to listen TCP socket
+
+=MCPC_EC_SVIMPL_TCP_INSUF_BUF=: insufficient buffer capacity
+
+=MCPC_EC_ANYDATA_NOTFOUND=: data cannot be found
+
+=MCPC_EC_ANYDATA_LONGNAME=: data's name is too long
+
+=MCPC_EC_PRMPTARG_NOTFOUND=: prompt agument cannot be found
+
+=MCPC_EC_JSON_FIELD_INVALID=: json field's value invalid, this means
+the field is missing, or field has unexpected type
+
+
+** =mcpc_anytype_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_anytype_t=
+:END:
+
+The data type recognizable by the library.
+
+*** Possible values
+
+=MCPC_NONE=: nothing
+
+=MCPC_I8=: =int8_t=-like type
+
+=MCPC_U8=: =uint8_t=-like type
+
+=MCPC_I16=: =int16_t=-like type
+
+=MCPC_U16=: =uint16_t=-like type
+
+=MCPC_I32=: =int32_t=-like type
+
+=MCPC_U32=: =uint32_t=-like type
+
+=MCPC_I64=: =int64_t=-like type
+
+=MCPC_U64=: =uint64_t=-like type
+
+=MCPC_U8STR=: string of UTF-8 code units
+
+=MCPC_I8S=: an array of =int8_t=
+
+=MCPC_U8S=: an array of =uint8_t=
+
+=MCPC_I16S=: an array of =int16_t=
+
+=MCPC_U16S=: an array of =uint16_t=
+
+=MCPC_I32S=: an array of =int32_t=
+
+=MCPC_U32S=: an array of =uint32_t=
+
+=MCPC_I64S=: an array of =int64_t=
+
+=MCPC_U64S=: an array of =uint64_t=
+
+=MCPC_FUNC=: a function
+
+
+** =mcpc_mime_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_mime_t=
+:END:
+
+MIME types recognizable by the library, that conforms to [[https://www.rfc-editor.org/rfc/rfc2045][RFC2045]].
+
+*** Possible value
+
+=MCPC_MIME_NONE=: nothing
+
+=MCPC_MIME_TEXT_PLAIN=: text/plain
+
+=MCPC_MIME_IMAGE_PNG=: image/png
+
+=MCPC_MIME_IMAGE_JPEG=: image/jpeg
+
+=MCPC_MIME_IMAGE_GIF=: image/gif
+
+=MCPC_MIME_AUDIO_MPEG=: audio/mpeg
+
+=MCPC_MIME_AUDIO_WAV=: audio/wav
+
+=MCPC_MIME_VIDEO_MPEG=: video/mpeg
+
+=MCPC_MIME_VIDEO_AVI=: video/avi
+
+=MCPC_MIME_APPLI_OCTS=: application/octet-stream
+
+=MCPC_MIME_APPLI_JSON=: application/json
+
+=MCPC_MIME_APPLI_PDF=: application/pdf
+
+
+** =mcpc_ucbr_t=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_ucbr_t=
+:END:
+
+User call back result.
+
+
+** =mcpc_ucbr_toolcall_add_text_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_ucbr_toolcall_add_text_printf8=
+:END:
+
+Add a text message to the result of /tools/call/.
+
+#+begin_src
+void
+mcpc_ucbr_toolcall_add_text_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+#+end_src
+
+=ucbr=: the callback result
+
+=fmt=: format string to be printed
+
+
+** =mcpc_ucbr_toolcall_add_errmsg_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_ucbr_toolcall_add_errmsg_printf8=
+:END:
+
+Add a error message to the result of /tools/call/.
+
+#+begin_src
+void
+mcpc_ucbr_toolcall_add_errmsg_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+#+end_src
+
+=ucbr=: the callback result
+
+=fmt=: format string to be printed
+
+
+** =mcpc_ucbr_prmptget_add_user_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_ucbr_prmptget_add_user_printf8=
+:END:
+
+Add a user prompt to the result of /prompts/get/.
+
+#+begin_src
+void
+mcpc_ucbr_prmptget_add_user_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+#+end_src
+
+=ucbr=: the callback result
+
+=fmt=: format string to be printed
+
+
+** =mcpc_ucbr_prmptget_add_assist_printf8=
+:PROPERTIES:
+:CUSTOM_ID: =mcpc_ucbr_prmptget_add_assist_printf8=
+:END:
+
+Add an assistant prompt to the result of /prompts/get/.
+
+#+begin_src
+void
+mcpc_ucbr_prmptget_add_assist_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt, ...);
+#+end_src
+
+=ucbr=: the callback result
+
+=fmt=: format string to be printed
+
diff --git a/third_party/mcpc/misc/init-msvc.bat b/third_party/mcpc/misc/init-msvc.bat
new file mode 100644
index 0000000..46b64a5
--- /dev/null
+++ b/third_party/mcpc/misc/init-msvc.bat
@@ -0,0 +1,5 @@
+
+"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
+
+@REM previous
+@REM "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat"
diff --git a/third_party/mcpc/mk/ccdefs-cygwin.mk b/third_party/mcpc/mk/ccdefs-cygwin.mk
new file mode 100644
index 0000000..d41e814
--- /dev/null
+++ b/third_party/mcpc/mk/ccdefs-cygwin.mk
@@ -0,0 +1,34 @@
+ifneq (,$(and $(isnot_done_ccdefs),$(is_cygwin)))
+ is_unix = xxx
+ is_unixar = xxx
+ is_winld = xxx
+
+ # currently we enforce gcc on cygwin
+ CC = gcc
+ CFLAGS += -Dis_unix
+ CFLAGS += -Dis_cygwin
+ is_gcc = xxx
+ is_gcclike = xxx
+ gcc_ver = $(shell $(CC) --version)
+ is_gcc12 = $(findstring 12.,$(gcc_ver))
+ is_gcc13 = $(findstring 13.,$(gcc_ver))
+ is_gcc14 = $(findstring 14.,$(gcc_ver))
+
+ ifeq (1,2)
+ else ifneq (,$(and $(is_gcc12)))
+ # deb12
+ CFLAGS += -std=c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc14)))
+ # fc41
+ CFLAGS += -std=c23
+ CFLAGS += -DMCPC_C23PTCH_UCHAR2
+ isnot_done_ccdefs =
+ endif
+
+
+
+endif
diff --git a/third_party/mcpc/mk/ccdefs-linux.mk b/third_party/mcpc/mk/ccdefs-linux.mk
new file mode 100644
index 0000000..b59540a
--- /dev/null
+++ b/third_party/mcpc/mk/ccdefs-linux.mk
@@ -0,0 +1,79 @@
+ifneq (,$(and $(isnot_done_ccdefs),$(is_linux)))
+ is_unix = xxx
+ is_unixar = xxx
+ CFLAGS += -Dis_unix
+
+ cc_ver = $(shell $(CC) --version)
+ ifneq (,$(findstring Free Software Foundation,$(cc_ver)))
+ found_gcc = xxx
+ endif
+ ifneq (,$(findstring clang,$(cc_ver)))
+ found_clang = xxx
+ endif
+
+ ifneq (,$(found_gcc))
+ is_gcc = xxx
+ is_gcclike = xxx
+ gcc_ver = $(shell $(CC) --version)
+ is_gcc10 = $(findstring 10.,$(gcc_ver))
+ is_gcc12 = $(findstring 12.,$(gcc_ver))
+ is_gcc13 = $(findstring 13.,$(gcc_ver))
+ is_gcc14 = $(findstring 14.,$(gcc_ver))
+ else ifneq (,$(found_clang))
+ is_clang = xxx
+ is_gcclike = xxx
+ clang_ver = $(shell $(CC) --version)
+ is_clang18 = $(findstring 18.,$(clang_ver))
+ is_clang19 = $(findstring 19.,$(clang_ver))
+ endif
+
+ ldd_ver = $(shell ldd --version)
+ is_glibc231 = $(and $(findstring Free Software Foundation,$(ldd_ver)),$(findstring 2.31,$(ldd_ver)))
+ is_glibc236 = $(and $(findstring Free Software Foundation,$(ldd_ver)),$(findstring 2.36,$(ldd_ver)))
+ is_glibc240 = $(and $(findstring Free Software Foundation,$(ldd_ver)),$(findstring 2.40,$(ldd_ver)))
+ is_cygwin = $(and $(findstring cygwin,$(ldd_ver)))
+
+ ifeq (1,2)
+ else ifneq (,$(and $(is_gcc10),$(is_glibc231)))
+ # deb11
+ CFLAGS += -std=c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ CFLAGS += -DMCPC_C23GIVUP_FIXENUM
+ is_manual_pthread = xxx
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc12),$(is_glibc236)))
+ # deb12
+ CFLAGS += -std=c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ CFLAGS += -DMCPC_C23GIVUP_FIXENUM
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc14),$(is_cygwin)))
+ # fc41
+ CFLAGS += -std=c23
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc14),$(is_mingw)))
+ # fc41
+ CFLAGS += -std=c233
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc14),$(is_glibc240)))
+ # fc41
+ CFLAGS += -std=c23
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_clang18),$(is_glibc240)))
+ # fc41
+ CFLAGS += -std=c23
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_clang19),$(is_glibc240)))
+ # fc41
+ CFLAGS += -std=c23
+ isnot_done_ccdefs =
+ endif
+
+
+
+endif
diff --git a/third_party/mcpc/mk/ccdefs-macos.mk b/third_party/mcpc/mk/ccdefs-macos.mk
new file mode 100644
index 0000000..8e38be5
--- /dev/null
+++ b/third_party/mcpc/mk/ccdefs-macos.mk
@@ -0,0 +1,53 @@
+ifneq (,$(and $(isnot_done_ccdefs),$(is_macos)))
+ is_unix = xxx
+ is_unixar = xxx
+ CFLAGS += -Dis_unix
+ cc_ver = $(shell $(CC) --version)
+ ifneq (,$(findstring Free Software Foundation,$(cc_ver)))
+ found_gcc = xxx
+ endif
+ ifneq (,$(findstring clang,$(cc_ver)))
+ found_clang = xxx
+ endif
+
+ ifneq (,$(found_gcc))
+ is_gcc = xxx
+ is_gcclike = xxx
+ gcc_ver = $(shell $(CC) --version)
+ is_gcc12 = $(findstring 12.,$(gcc_ver))
+ is_gcc13 = $(findstring 13.,$(gcc_ver))
+ is_gcc14 = $(findstring 14.,$(gcc_ver))
+ else ifneq (,$(found_clang))
+ is_clang = xxx
+ is_gcclike = xxx
+ clang_ver = $(shell $(CC) --version)
+ is_clang14 = $(findstring 14.,$(clang_ver))
+ is_clang15 = $(findstring 15.,$(clang_ver))
+ is_clang16 = $(findstring 16.,$(clang_ver))
+ endif
+
+ ifeq (1,2)
+ else ifneq (,$(and $(is_clang14)))
+ CFLAGS += -std=c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ CFLAGS += -Dno_c23_n2508
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_clang15)))
+ CFLAGS += -std=c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ CFLAGS += -Dno_c23_n2508
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_clang16)))
+ CFLAGS += -std=c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ CFLAGS += -Dno_c23_n2508
+ isnot_done_ccdefs =
+ endif
+
+endif
diff --git a/third_party/mcpc/mk/ccdefs-mingw.mk b/third_party/mcpc/mk/ccdefs-mingw.mk
new file mode 100644
index 0000000..4b36d0b
--- /dev/null
+++ b/third_party/mcpc/mk/ccdefs-mingw.mk
@@ -0,0 +1,89 @@
+ifneq (,$(and $(isnot_done_ccdefs),$(is_mingw)))
+ is_unix = xxx
+ CFLAGS += -Dis_mingw
+ CFLAGS += -Dis_win # winapi?
+
+ wherecl = $(shell where cl)
+ ifneq (,$(wherecl))
+ found_cl = xxx
+ endif
+
+ # Use cl whenever possible
+ ifneq (,$(and $(found_cl),$(is_indirsh)))
+ CC = cl
+ is_cllike = xxx
+ is_winlib = xxx
+ else
+ # To find any other cc
+
+ # gh win2019 patch, intentionally to use gcc
+ ifneq (,$(is_win2019))
+ CC = gcc
+ endif
+ # GH win2022 patch, intentionally to use gcc
+ ifneq (,$(is_win2022))
+ CC = gcc
+ endif
+
+ cc_ver = $(shell $(CC) --version)
+ ifneq (,$(findstring Free Software Foundation,$(cc_ver)))
+ found_gcc = xxx
+ endif
+ ifneq (,$(found_gcc))
+ is_gcc = xxx
+ is_gcclike = xxx
+ is_unixar = xxx
+ is_winld = xxx
+ gcc_ver = $(shell $(CC) --version)
+ is_gcc8 = $(findstring 8.,$(gcc_ver))
+ is_gcc12 = $(findstring 12.,$(gcc_ver))
+ is_gcc13 = $(findstring 13.,$(gcc_ver))
+ is_gcc14 = $(findstring 14.,$(gcc_ver))
+ endif
+ ifneq (,$(found_clang))
+ is_clang = xxx
+ is_gcclike = xxx
+ is_unixar = xxx
+ is_winld = xxx
+ clang_ver = $(shell $(CC) --version)
+ is_clang18 = $(findstring 18.,$(clang_ver))
+ is_clang19 = $(findstring 19.,$(clang_ver))
+ endif
+ endif
+
+ ifeq (1,2)
+ else ifneq (,$(is_cllike))
+ CFLAGS += /Dis_win
+ CFLAGS += /Dis_wincl
+ CFLAGS += /nologo
+ CFLAGS += /std:c17
+ CFLAGS += /DMCPC_C23PTCH_KW1
+ CFLAGS += /DMCPC_C23PTCH_CKD1
+ CFLAGS += /DMCPC_C23PTCH_UCHAR1
+ CFLAGS += /DMCPC_C23GIVUP_FIXENUM
+ LDFLAGS += /nologo
+ is_cllike = xxx
+ is_winlib = xxx
+ is_cl = xxx
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc8)))
+ CFLAGS += -std=c11
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ CFLAGS += -DMCPC_C23GIVUP_FIXENUM
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc12)))
+ CFLAGS += -std=c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ CFLAGS += -DMCPC_C23GIVUP_FIXENUM
+ isnot_done_ccdefs =
+ else ifneq (,$(and $(is_gcc14)))
+ # fc41
+ CFLAGS += -std=c23
+ CFLAGS += -DMCPC_C23PTCH_UCHAR2
+ isnot_done_ccdefs =
+ endif
+endif
diff --git a/third_party/mcpc/mk/ccdefs-win.mk b/third_party/mcpc/mk/ccdefs-win.mk
new file mode 100644
index 0000000..76e9246
--- /dev/null
+++ b/third_party/mcpc/mk/ccdefs-win.mk
@@ -0,0 +1,60 @@
+ifneq (,$(and $(isnot_done_ccdefs),$(is_win)))
+
+ ifdef USE_WINCLANG
+ CC = clang-cl
+ CFLAGS += /Dis_win
+ is_cllike = xxx
+ is_clang = xxx
+ is_winclang = xxx
+ clang_ver = $(shell $(CC) --version)
+ is_clang14 = $(findstring 14.,$(clang_ver))
+ is_clang16 = $(findstring 16.,$(clang_ver))
+ is_clang19 = $(findstring 19.,$(clang_ver))
+
+ ifeq (1,2)
+ else ifneq (,$(and $(is_clang14)))
+ CFLAGS += /std:c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ else ifneq (,$(and $(is_clang16)))
+ CFLAGS += /std:c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ else ifneq (,$(and $(is_clang19)))
+ CFLAGS += /std:c17
+ CFLAGS += -DMCPC_C23PTCH_KW1
+ CFLAGS += -DMCPC_C23PTCH_CKD1
+ CFLAGS += -DMCPC_C23PTCH_UCHAR1
+ endif
+
+ else
+
+ ifneq (,$(found_cl))
+ CC = cl
+ CFLAGS += /Dis_win
+ CFLAGS += /Dis_wincl
+ CFLAGS += /nologo
+ CFLAGS += /std:c17
+ CFLAGS += /DMCPC_C23PTCH_KW1
+ CFLAGS += /DMCPC_C23PTCH_CKD1
+ CFLAGS += /DMCPC_C23PTCH_UCHAR1
+ CFLAGS += /DMCPC_C23GIVUP_FIXENUM
+ LDFLAGS += /nologo
+ is_cllike = xxx
+ is_winlib = xxx
+ is_cl = xxx
+ isnot_done_ccdefs =
+ else
+ cc_ver = $(shell $(CC) --version)
+ ifneq (,$(findstring Free Software Foundation,$(ccver)))
+ is_gcc = xxx
+ is_gcclike = xxx
+ is_unixar = xxx
+ is_winld = xxx
+ endif
+ endif
+ endif
+
+endif
diff --git a/third_party/mcpc/mk/dbgrel.mk b/third_party/mcpc/mk/dbgrel.mk
new file mode 100644
index 0000000..a32e0bd
--- /dev/null
+++ b/third_party/mcpc/mk/dbgrel.mk
@@ -0,0 +1,92 @@
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# dbgrel
+
+ifdef DBG
+ ifeq (1,2)
+ else ifneq (,$(is_gcclike))
+ CFLAGS += -g -O0
+ CFLAGS += -Wall -Wextra -Werror -Wno-unused-function -Wno-unused-parameter -Wno-unused-label
+ CFLAGS += -DMCPC_DBG
+ CFLAGS += -I..
+ else ifneq (,$(is_cllike))
+ CFLAGS += /Zi /Od /Ob0
+ CFLAGS += /RTC1
+ # CFLAGS += /Wall
+ CFLAGS += /DMCPC_DBG
+ CFLAGS += /I..
+ endif
+else
+ ifeq (1,2)
+ else ifneq (,$(is_gcclike))
+ CFLAGS += -Wall -Wextra -Werror -Wno-unused-function -Wno-unused-parameter -Wno-unused-label -Wno-error=unused-variable -Wno-error=unused-but-set-variable
+ CFLAGS += -O2 -Os
+ CFLAGS += -I..
+ else ifneq (,$(is_cllike))
+ CFLAGS += /MD
+ CFLAGS += /Wall
+ CFLAGS += /wd4152 # okay with: nonstandard extension, function/data pointer conversion in expression
+ CFLAGS += /wd4310 # okay with cast causing truncate
+ CFLAGS += /wd4127 # window is wrong about sizeof(long) being constant
+ CFLAGS += /wd4102 # okay to have unref label
+ CFLAGS += /wd4189 # local variable is initialized but not referenced
+ CFLAGS += /wd4100 # its okay not ref to param
+ CFLAGS += /wd5045 # why call spectre rather than boundcheck?
+ CFLAGS += /wd4005 # alloca macro redef is ok
+ CFLAGS += /wd4996 # use strerror sprintf anyway
+ CFLAGS += /wd4456 # no need to warn shadow decl
+ CFLAGS += /wd4255 # ok for C23
+ CFLAGS += /wd4668 # disb #if macro
+ CFLAGS += /wd4710 # disable inline warn
+ CFLAGS += /wd4820 # we know there is padding
+ CFLAGS += /O2 /Ob1
+ CFLAGS += /I..
+ endif
+endif
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# strip
+
+ifndef DBG
+ # LDFLAGS += --strip-all ld not recoginizing?
+ ifneq (,$(is_linux))
+ LDFLAGS += -s
+ endif
+ ifneq (,$(is_macos))
+ LDFLAGS += -dead_strip
+ endif
+endif
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# sanitizer
+
+ifdef VALG
+ which_valg = $(shell which valgrind)
+ ifneq (,$(which_valg))
+ is_valg = xxx
+ endif
+endif
+
+
+# NOTE asan triggered only if ASAN=1, we use valgrind by default
+ifdef ASAN
+ is_asan = xxx
+ is_valg =
+ ifneq (,$(is_linux))
+ CFLAGS += -fsanitize=address
+ LDFLAGS += -lasan
+ endif
+ ifneq (,$(is_macos))
+ CFLAGS += -fsanitize=address
+ LDFLAGS += -fsanitize=address
+ endif
+ ifneq (,$(is_cllike))
+ CFLAGS += /fsanitize=address
+ LDFLAGS += /DEBUG
+# LDFLAGS += "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\lib\\clang\\19\\lib\\windows\\clang_rt.asan-i386.lib"
+ LDFLAGS += /NODEFAULTLIB:libcmt.lib
+ LDFLAGS += msvcrt.lib
+ endif
+endif
+
+
diff --git a/third_party/mcpc/mk/step0.mk b/third_party/mcpc/mk/step0.mk
new file mode 100644
index 0000000..8ec5d72
--- /dev/null
+++ b/third_party/mcpc/mk/step0.mk
@@ -0,0 +1,31 @@
+ifeq ($(SHELL),/bin/sh)
+ is_sh = xxx
+ is_dirsh = xxx
+ unameall = $(shell uname -a)
+ is_linux = $(findstring Linux,$(unameall))
+ is_cygwin = $(findstring Cygwin,$(unameall))
+ is_mingw = $(findstring MINGW64_NT,$(unameall))
+ is_macos = $(findstring Darwin,$(unameall))
+else ifneq (,$(findstring /sh.exe,$(SHELL)))
+ # indirectly enter bash (but the only way we enter GH win)
+ is_sh = xxx
+ is_indirsh = xxx
+ is_win = xxx
+ unameall = $(shell uname -a)
+ is_msys = $(findstring MSYS_NT,$(unameall))
+ is_mingw = $(findstring MINGW64_NT,$(unameall))
+ sysinfo = $(shell systeminfo)
+ ifneq (,$(findstring Windows Server 2019,$(sysinfo)))
+ is_win2019 = xxx
+ endif
+ ifneq (,$(findstring Windows Server 2022,$(sysinfo)))
+ is_win2022 = xxx
+ endif
+ ifneq (,$(findstring Windows Server 2025,$(sysinfo)))
+ is_win2025 = xxx
+ endif
+else ifneq (,$(findstring sh.exe,$(SHELL)))
+ is_win = xxx
+ is_cmd = xxx
+endif
+isnot_done_ccdefs = xxx
diff --git a/third_party/mcpc/src/.indent.pro b/third_party/mcpc/src/.indent.pro
new file mode 100644
index 0000000..f7bb493
--- /dev/null
+++ b/third_party/mcpc/src/.indent.pro
@@ -0,0 +1 @@
+../.indent.pro \ No newline at end of file
diff --git a/third_party/mcpc/src/CMakeLists.txt b/third_party/mcpc/src/CMakeLists.txt
new file mode 100644
index 0000000..6933543
--- /dev/null
+++ b/third_party/mcpc/src/CMakeLists.txt
@@ -0,0 +1,32 @@
+set (libmcpc_SOURCES)
+
+# add_compile_options(-std=c23)
+# add_compile_options(-Dis_unix)
+
+add_compile_options (/Dis_win)
+add_compile_options (/Dis_wincl)
+add_compile_options (/nologo)
+add_compile_options (/std:c17)
+add_compile_options (/DMCPC_PTCH_C23KW1)
+add_compile_options (/DMCPC_PTCH_C23CKD1)
+add_compile_options (/DMCPC_PTCH_C23UCHAR1)
+
+
+list (APPEND libmcpc_SOURCES alloc.c)
+list (APPEND libmcpc_SOURCES log.c)
+list (APPEND libmcpc_SOURCES errcode.c)
+list (APPEND libmcpc_SOURCES tool.c)
+list (APPEND libmcpc_SOURCES rsc.c)
+list (APPEND libmcpc_SOURCES server.c)
+list (APPEND libmcpc_SOURCES retbuf.c)
+list (APPEND libmcpc_SOURCES ucbr.c)
+list (APPEND libmcpc_SOURCES serlz.c)
+list (APPEND libmcpc_SOURCES mjson.c)
+
+add_library (mcpc STATIC ${libmcpc_SOURCES})
+
+target_include_directories (
+ mcpc
+ PRIVATE ${PROJECT_SOURCE_DIR}
+)
+target_link_libraries (mcpc ws2_32)
diff --git a/third_party/mcpc/src/Makefile b/third_party/mcpc/src/Makefile
new file mode 100644
index 0000000..b9c5c4c
--- /dev/null
+++ b/third_party/mcpc/src/Makefile
@@ -0,0 +1,94 @@
+include ../mk/step0.mk
+
+ifneq (,$(is_linux))
+ include ../mk/ccdefs-linux.mk
+endif
+ifneq (,$(is_cygwin))
+ include ../mk/ccdefs-cygwin.mk
+endif
+ifneq (,$(is_mingw))
+ include ../mk/ccdefs-mingw.mk
+endif
+ifneq (,$(is_macos))
+ include ../mk/ccdefs-macos.mk
+endif
+ifneq (,$(is_win))
+ include ../mk/ccdefs-win.mk
+endif
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+include ../mk/dbgrel.mk
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+default: link
+
+clean:
+ $(if $(is_cmd) , del *.pdb *.ilk *.o *.out *.a *.so *.dSYM *.obj *.exe *.lib *.dll *.exp )
+ $(if $(is_sh) , rm -rf *.pdb *.ilk *.o *.out *.a *.so *.dSYM *.obj *.exe *.lib *.dll *.exp )
+
+comp: libmcpc_COMP
+
+link: libmcpc_LINKS
+link: libmcpc_LINKD
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# libmcpc
+
+libmcpc_srcs =
+libmcpc_srcs += alloc.c
+libmcpc_srcs += log.c
+libmcpc_srcs += errcode.c
+libmcpc_srcs += anydata.c
+libmcpc_srcs += tool.c
+libmcpc_srcs += rsc.c
+libmcpc_srcs += prmpt.c
+libmcpc_srcs += server.c
+libmcpc_srcs += retbuf.c
+libmcpc_srcs += ucbr.c
+libmcpc_srcs += complt.c
+libmcpc_srcs += serlz.c
+libmcpc_srcs += mjson.c
+
+libmcpc_objs =
+ifneq (,$(is_gcclike))
+ libmcpc_objs = $(patsubst %.c,%.o,$(libmcpc_srcs))
+endif
+ifneq (,$(is_cllike))
+ libmcpc_objs = $(patsubst %.c,%.obj,$(libmcpc_srcs))
+endif
+
+libmcpc_cflags =
+ifeq (,$(is_win))
+ libmcpc_cflags += -fPIC
+endif
+libmcpc_clcflags =
+
+libmcpc_ldflags =
+ifneq (,$(is_unixar))
+ libmcpc_ldflags += ../src/libmcpc.a
+endif
+ifneq (,$(is_winlib))
+ libmcpc_ldflags += ws2_32.lib
+endif
+ifneq (,$(is_winld))
+ libmcpc_ldflags += -lws2_32
+endif
+
+libmcpc_arflags =
+libmcpc_arflags += rcs
+
+libmcpc_COMP:
+ $(if $(is_gcclike) , $(CC) $(CFLAGS) $(libmcpc_cflags) $(libmcpc_srcs) -c )
+ $(if $(is_cllike) , $(CC) $(CFLAGS) $(libmcpc_clcflags) $(libmcpc_srcs) /c )
+
+libmcpc_LINKS: libmcpc_COMP
+ $(if $(is_gcclike) , $(AR) $(libmcpc_arflags) libmcpc.a $(libmcpc_objs) )
+ $(if $(is_cllike) , lib /nologo $(libmcpc_objs) /out:mcpc.lib $(libmcpc_ldflags) )
+
+libmcpc_LINKD: libmcpc_COMP
+ $(if $(is_gcclike) , $(CC) $(LDFLAGS) -o libmcpc.so $(libmcpc_objs) -shared $(libmcpc_ldflags) )
+ $(if $(is_cllike) , cl /nologo $(libmcpc_objs) /link /dll /out:mcpc.dll $(LDFLAGS) $(libmcpc_ldflags) )
+
+
diff --git a/third_party/mcpc/src/alloc.c b/third_party/mcpc/src/alloc.c
new file mode 100644
index 0000000..4771274
--- /dev/null
+++ b/third_party/mcpc/src/alloc.c
@@ -0,0 +1,57 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "alloc.h"
+#include <string.h>
+
+// #define MCPC_CALLOC 1
+
+void *
+mcpc_alloc (const size_t nbytes)
+{
+ void *p =
+#ifdef MCPC_CALLOC
+ calloc (nbytes, 1)
+#else
+ malloc (nbytes)
+#endif
+ ;
+ return p;
+}
+
+void *
+mcpc_realloc (void *ptr, const size_t size)
+{
+ void *ret = realloc (ptr, size);
+
+#ifdef MCPC_CALLOC
+ memset (ret, 0, size);
+#endif
+
+ return ret;
+}
+
+void *
+mcpc_calloc (const size_t nbytes)
+{
+ void *p = calloc (nbytes, 1);
+ return p;
+}
diff --git a/third_party/mcpc/src/alloc.h b/third_party/mcpc/src/alloc.h
new file mode 100644
index 0000000..1742350
--- /dev/null
+++ b/third_party/mcpc/src/alloc.h
@@ -0,0 +1,39 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h__alloc
+#define h__alloc
+
+#if is_win || is_mingw
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+#include <stdlib.h>
+
+void *mcpc_alloc (const size_t nbytes);
+
+void *mcpc_realloc (void* ptr, const size_t size);
+
+void *mcpc_calloc (const size_t size);
+
+#endif
diff --git a/third_party/mcpc/src/anydata.c b/third_party/mcpc/src/anydata.c
new file mode 100644
index 0000000..0c663cd
--- /dev/null
+++ b/third_party/mcpc/src/anydata.c
@@ -0,0 +1,817 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "anydata.h"
+
+#include "ext_string.h"
+
+#include <mcpc/errcode.h>
+#include "alloc.h"
+#include "errcode.h"
+
+const mcpc_anydata_t *mcpc_anypool_getbymeta1 (const mcpc_anypool_t * anypool, const char8_t * meta1, size_t meta1_len);
+const mcpc_anydata_t *mcpc_anypool_getbymeta1nt (const mcpc_anypool_t * anypool, const char8_t * meta1nt);
+
+constexpr static size_t mcpc_anypool_gamt = 8;
+
+/* = = = = = = = = = = = = = = = = anydata = = = = = = = = = = = = = = = = */
+
+mcpc_anydata_t *
+mcpc_anydata_new ()
+{
+ mcpc_anydata_t *hp_data = mcpc_calloc (sizeof (mcpc_anydata_t));
+ return hp_data;
+}
+
+mcpc_anydata_t *
+mcpc_anydata_new_typ (mcpc_anytype_t typ)
+{
+ mcpc_anydata_t *hp_data = mcpc_calloc (sizeof (mcpc_anydata_t));
+ hp_data->data_typ = typ;
+ return hp_data;
+}
+
+mcpc_anydata_t *
+mcpc_anydata_new_meta1_meta2 (const char8_t *meta1, const size_t meta1_len, const char8_t *meta2, const size_t meta2_len)
+{
+ mcpc_assert (meta1_len <= (size_t) UINT8_MAX, MCPC_EC_ANYDATA_LONGMETA1);
+ mcpc_assert (meta2_len <= (size_t) UINT16_MAX, MCPC_EC_ANYDATA_LONGMETA2);
+
+ mcpc_anydata_t *hp_data = mcpc_calloc (sizeof (mcpc_anydata_t));
+
+ void *hp_meta1 = mcpc_alloc (meta1_len);
+ memcpy (hp_meta1, meta1, meta1_len);
+ hp_data->meta1 = hp_meta1;
+ hp_data->meta1_len = (u8_t) meta1_len;
+
+ if (meta2_len > 0)
+ {
+ void *hp_meta2 = mcpc_alloc (meta2_len);
+ memcpy (hp_meta2, meta2, meta2_len);
+ hp_data->meta2 = hp_meta2;
+ hp_data->meta2_len = (u8_t) meta2_len;
+ }
+
+ return hp_data;
+}
+
+mcpc_anydata_t *
+mcpc_anydata_new_meta1nt_meta2nt (const char8_t *meta1nt, const char8_t *meta2nt)
+{
+ return mcpc_anydata_new_meta1_meta2 (meta1nt, u8strlen (meta1nt), meta2nt, u8strlen (meta2nt));
+}
+
+mcpc_anydata_t *
+mcpc_anydata_new_typ_meta1 (mcpc_anytype_t typ, const char8_t *meta1, const size_t meta1_len)
+{
+ mcpc_anydata_t *hp_data = mcpc_anydata_new_meta1_meta2 (meta1, meta1_len, nullptr, 0);
+ hp_data->data_typ = typ;
+ return hp_data;
+}
+
+mcpc_anydata_t *
+mcpc_anydata_new_typ_meta1_meta2 (mcpc_anytype_t typ, const char8_t *meta1, const size_t meta1_len, const char8_t *meta2,
+ const size_t meta2_len)
+{
+ mcpc_anydata_t *hp_data = mcpc_anydata_new_meta1_meta2 (meta1, meta1_len, meta2, meta2_len);
+ hp_data->data_typ = typ;
+ return hp_data;
+}
+
+mcpc_anydata_t *
+mcpc_anydata_new_typ_meta1nt_meta2nt (mcpc_anytype_t typ, const char8_t *meta1nt, const char8_t *meta2nt)
+{
+ return mcpc_anydata_new_typ_meta1_meta2 (typ, meta1nt, u8strlen (meta1nt), meta2nt, u8strlen (meta2nt));
+}
+
+void
+mcpc_anydata_mbfree (mcpc_anydata_t *data)
+{
+ bug_if (data == nullptr);
+
+ if (data->meta1 != nullptr)
+ {
+ free ((void *) data->meta1);
+ data->meta1_len = 0;
+ }
+
+ if (data->meta2 != nullptr)
+ {
+ free ((void *) data->meta2);
+ data->meta2_len = 0;
+ }
+
+ if (data->chd_pool != nullptr)
+ {
+ mcpc_anypool_free (data->chd_pool);
+ }
+
+ if (mcpc_anytype_is_arrlike (data->data_typ))
+ {
+ free ((void *) data->data_arrlk);
+ data->data_len = 0;
+ }
+}
+
+void
+mcpc_anydata_free (mcpc_anydata_t *data)
+{
+ mcpc_anydata_mbfree (data);
+ free (data);
+}
+
+void
+mcpc_anydata_copy (mcpc_anydata_t *dest_data, const mcpc_anydata_t *src_data)
+{
+ bug_if (dest_data == nullptr);
+ bug_if (src_data == nullptr);
+
+ // make sure inited
+ memset (dest_data, 0, sizeof (mcpc_anydata_t));
+
+ if (src_data->meta1_len > 0 && src_data->meta1 != nullptr)
+ {
+ void *hp_meta1 = mcpc_alloc (src_data->meta1_len);
+ memcpy (hp_meta1, src_data->meta1, src_data->meta1_len);
+ dest_data->meta1 = hp_meta1;
+ dest_data->meta1_len = src_data->meta1_len;
+ }
+
+ if (src_data->meta2_len > 0 && src_data->meta2 != nullptr)
+ {
+ void *hp_meta2 = mcpc_alloc (src_data->meta2_len);
+ memcpy (hp_meta2, src_data->meta2, src_data->meta2_len);
+ dest_data->meta2 = hp_meta2;
+ dest_data->meta2_len = src_data->meta2_len;
+ }
+
+ if (false);
+ else if (mcpc_anytype_is_arrlike (src_data->data_typ) && src_data->data_len > 0)
+ {
+ dest_data->data_arrlk = mcpc_alloc (src_data->data_len);
+ memcpy (dest_data->data_arrlk, src_data->data_arrlk, src_data->data_len);
+ }
+ else if (mcpc_anytype_is_intlike (src_data->data_typ))
+ dest_data->data_intlk = src_data->data_intlk;
+
+ if (src_data->chd_pool != nullptr)
+ {
+ dest_data->chd_pool = mcpc_anypool_new ();
+ mcpc_anypool_copy (dest_data->chd_pool, src_data->chd_pool);
+ }
+
+ dest_data->data_typ = src_data->data_typ;
+ dest_data->data_len = src_data->data_len;
+ dest_data->flags = src_data->flags;
+ dest_data->data_mime = src_data->data_mime;
+
+}
+
+void
+mcpc_anydata_add_child (mcpc_anydata_t *data, mcpc_anydata_t *child)
+{
+ bug_if_nullptr (data);
+ bug_if_nullptr ((void *) child);
+
+ if (data->chd_pool == nullptr)
+ {
+ data->chd_pool = mcpc_anypool_new ();
+ }
+ mcpc_anypool_addfre (data->chd_pool, child);
+}
+
+void
+mcpc_anydata_addcpy_child (mcpc_anydata_t *data, const mcpc_anydata_t *child)
+{
+ bug_if_nullptr (data);
+ bug_if_nullptr ((void *) child);
+
+ if (data->chd_pool == nullptr)
+ {
+ data->chd_pool = mcpc_anypool_new ();
+ }
+ mcpc_anypool_addcpy (data->chd_pool, child);
+}
+
+const mcpc_anydata_t *
+mcpc_anydata_get_child (const mcpc_anydata_t *anydata, size_t idx)
+{
+ bug_if (anydata == nullptr);
+ return mcpc_anypool_get (anydata->chd_pool, idx);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_meta1 (mcpc_anydata_t *anydata, const char8_t *meta1, size_t meta1_len)
+{
+ bug_if_nullptr (anydata);
+ bug_if_nullptr ((void *) meta1);
+
+ if (meta1_len == 0)
+ {
+ free ((void *) anydata->meta1);
+ anydata->meta1 = nullptr;
+ anydata->meta1_len = 0;
+ return MCPC_EC_0;
+ }
+
+ if (meta1_len > (size_t) UINT8_MAX)
+ {
+ return MCPC_EC_ANYDATA_LONGMETA1;
+ }
+
+ anydata->meta1 = mcpc_realloc ((void *) anydata->meta1, meta1_len);
+ memcpy ((void *) anydata->meta1, meta1, meta1_len);
+ anydata->meta1_len = (u8_t) meta1_len;
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_meta1nt (mcpc_anydata_t *anydata, const char8_t *meta1)
+{
+ size_t meta1_len = u8strlen (meta1);
+ return mcpc_anydata_set_meta1 (anydata, meta1, meta1_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_release_data (mcpc_anydata_t *anydata)
+{
+ if (!mcpc_anytype_is_arrlike (anydata->data_typ))
+ return MCPC_EC_0;
+
+ if (anydata->data_len == 0)
+ return MCPC_EC_0;
+
+ free (anydata->data_arrlk);
+ anydata->data_len = 0;
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_intlk (mcpc_anydata_t *anydata, mcpc_anytype_t typ, const void *val)
+{
+ bug_if_nullptr (anydata);
+
+ if (mcpc_anytype_is_arrlike (anydata->data_typ))
+ mcpc_anydata_release_data (anydata);
+
+ if (false);
+ else if (typ == MCPC_I8 || typ == MCPC_U8)
+ anydata->data_intlk = (uintmax_t) (*((u8_t *) val));
+ else if (typ == MCPC_I16 || typ == MCPC_U16)
+ anydata->data_intlk = (uintmax_t) (*((u16_t *) val));
+ else if (typ == MCPC_I32 || typ == MCPC_U32)
+ anydata->data_intlk = (uintmax_t) (*((u32_t *) val));
+ else if (typ == MCPC_I64 || typ == MCPC_U64)
+ anydata->data_intlk = (uintmax_t) (*((u64_t *) val));
+ else if (typ == MCPC_FUNC)
+ anydata->data_intlk = (uintmax_t) (uintptr_t) (val);
+ else
+ return MCPC_EC_BUG;
+
+ anydata->data_typ = typ;
+ anydata->data_len = 1;
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_intlk (const mcpc_anydata_t *anydata, mcpc_anytype_t typ, void *ret)
+{
+ bug_if_nullptr (anydata);
+ bug_if_nullptr (ret);
+
+ if (anydata->data_typ != typ)
+ return MCPC_EC_ANYDATA_NOTFOUND;
+
+ if (false);
+ else if (typ == MCPC_I8)
+ {
+ i8_t *ret_typed = ret;
+ *ret_typed = (i8_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_U8)
+ {
+ u8_t *ret_typed = ret;
+ *ret_typed = (u8_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_I16)
+ {
+ i16_t *ret_typed = ret;
+ *ret_typed = (i16_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_U16)
+ {
+ u16_t *ret_typed = ret;
+ *ret_typed = (u16_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_I32)
+ {
+ i32_t *ret_typed = ret;
+ *ret_typed = (i32_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_U32)
+ {
+ u32_t *ret_typed = ret;
+ *ret_typed = (u32_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_I64)
+ {
+ i64_t *ret_typed = ret;
+ *ret_typed = (i64_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_U64)
+ {
+ u64_t *ret_typed = ret;
+ *ret_typed = (u64_t) anydata->data_intlk;
+ }
+ else if (typ == MCPC_FUNC)
+ {
+ uintptr_t *ret_typed = ret;
+ *ret_typed = (uintptr_t) anydata->data_intlk;
+ }
+ else
+ return MCPC_EC_BUG;
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_arrlk (mcpc_anydata_t *anydata, mcpc_anytype_t typ, const void *arr, size_t arr_len)
+{
+ bug_if_nullptr (anydata);
+ bug_if_nullptr (arr);
+
+ size_t nbytes = 0;
+
+ if (false);
+ else if (typ == MCPC_I8S || typ == MCPC_U8S)
+ nbytes = arr_len * sizeof (u8_t);
+ else if (typ == MCPC_I16S || typ == MCPC_U16S)
+ nbytes = arr_len * sizeof (u16_t);
+ else if (typ == MCPC_I32S || typ == MCPC_U32S)
+ nbytes = arr_len * sizeof (u32_t);
+ else if (typ == MCPC_I64S || typ == MCPC_U64S)
+ nbytes = arr_len * sizeof (u64_t);
+ else if (typ == MCPC_U8STR)
+ nbytes = arr_len * sizeof (char8_t);
+ else
+ return MCPC_EC_BUG;
+
+ if (anydata->data_typ == typ)
+ {
+ anydata->data_arrlk = mcpc_realloc (anydata->data_arrlk, nbytes);
+ }
+ else
+ {
+ if (mcpc_anytype_is_arrlike (anydata->data_typ))
+ mcpc_anydata_release_data (anydata);
+
+ anydata->data_typ = typ;
+ anydata->data_arrlk = mcpc_alloc (nbytes);
+ }
+ memcpy (anydata->data_arrlk, arr, nbytes);
+ anydata->data_len = arr_len;
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_arrlk (const mcpc_anydata_t *anydata, mcpc_anytype_t typ, void *ret, size_t ret_cap, size_t *ret_len)
+{
+ // NOTE ret_len can be nullptr
+
+ if (anydata->data_typ != typ)
+ return MCPC_EC_ANYDATA_NOTFOUND;
+ if (ret_cap == 0)
+ return MCPC_EC_ANYDATA_NOTFOUND;
+
+ size_t nb_el = 0;
+ if (false);
+ else if (typ == MCPC_I8S || typ == MCPC_U8S)
+ nb_el = sizeof (u8_t);
+ else if (typ == MCPC_I16S || typ == MCPC_U16S)
+ nb_el = sizeof (u16_t);
+ else if (typ == MCPC_I32S || typ == MCPC_U32S)
+ nb_el = sizeof (u32_t);
+ else if (typ == MCPC_I64S || typ == MCPC_U64S)
+ nb_el = sizeof (u64_t);
+ else if (typ == MCPC_U8STR)
+ nb_el = sizeof (char8_t);
+ else
+ return MCPC_EC_BUG;
+
+ size_t cplen = anydata->data_len;
+ if (cplen > ret_cap)
+ cplen = ret_cap;
+
+ memcpy (ret, anydata->data_arrlk, cplen * nb_el);
+ if (ret_len != nullptr)
+ *ret_len = cplen;
+
+ if (ret_cap > cplen)
+ {
+ char8_t *ret_typed = ret;
+ ret_typed[cplen] = 0;
+ return MCPC_EC_0;
+ }
+ else
+ {
+ return MCPC_EC_BUFCAP;
+ }
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i8 (mcpc_anydata_t *anydata, i8_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_I8, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u8 (mcpc_anydata_t *anydata, u8_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_U8, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i16 (mcpc_anydata_t *anydata, i16_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_I16, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u16 (mcpc_anydata_t *anydata, u16_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_U16, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i32 (mcpc_anydata_t *anydata, i32_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_I32, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u32 (mcpc_anydata_t *anydata, u32_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_U32, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i64 (mcpc_anydata_t *anydata, i64_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_I64, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u64 (mcpc_anydata_t *anydata, u64_t val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_U64, &val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_func (mcpc_anydata_t *anydata, void *val)
+{
+ return mcpc_anydata_set_intlk (anydata, MCPC_FUNC, val);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i8 (const mcpc_anydata_t *anydata, i8_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_I8, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u8 (const mcpc_anydata_t *anydata, u8_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_U8, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i16 (const mcpc_anydata_t *anydata, i16_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_I16, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u16 (const mcpc_anydata_t *anydata, u16_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_U16, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i32 (const mcpc_anydata_t *anydata, i32_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_I32, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u32 (const mcpc_anydata_t *anydata, u32_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_U32, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i64 (const mcpc_anydata_t *anydata, i64_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_I64, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u64 (const mcpc_anydata_t *anydata, u64_t *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_U64, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_func (const mcpc_anydata_t *anydata, void *ret)
+{
+ return mcpc_anydata_get_intlk (anydata, MCPC_FUNC, ret);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i8s (mcpc_anydata_t *anydata, const i8_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_I8S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u8s (mcpc_anydata_t *anydata, const u8_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_U8S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i16s (mcpc_anydata_t *anydata, const i16_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_I16S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u16s (mcpc_anydata_t *anydata, const u16_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_U16S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i32s (mcpc_anydata_t *anydata, const i32_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_I32S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u32s (mcpc_anydata_t *anydata, const u32_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_U32S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_i64s (mcpc_anydata_t *anydata, const i64_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_I64S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u64s (mcpc_anydata_t *anydata, const u64_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_U64S, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_set_u8str (mcpc_anydata_t *anydata, const char8_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_arrlk (anydata, MCPC_U8STR, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i8s (const mcpc_anydata_t *anydata, i8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_I8S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u8s (const mcpc_anydata_t *anydata, u8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_U8S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i16s (const mcpc_anydata_t *anydata, i16_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_I16S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u16s (const mcpc_anydata_t *anydata, u16_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_U16S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i32s (const mcpc_anydata_t *anydata, i32_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_I32S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u32s (const mcpc_anydata_t *anydata, u32_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_U32S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_i64s (const mcpc_anydata_t *anydata, i64_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_I64S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u64s (const mcpc_anydata_t *anydata, u64_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_U64S, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_get_u8str (const mcpc_anydata_t *anydata, char8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_arrlk (anydata, MCPC_U8STR, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_anydata_getref_u8str (const mcpc_anydata_t *anydata, const char8_t **ret, size_t *ret_len)
+{
+ bug_if_nullptr (anydata);
+ bug_if_nullptr (ret);
+ bug_if_nullptr (ret_len);
+
+ *ret = (const char8_t *) anydata->data_arrlk;
+ *ret_len = anydata->data_len;
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_anydata_getref_meta1 (const mcpc_anydata_t *anydata, const char8_t **ret, size_t *ret_len)
+{
+ bug_if_nullptr (anydata);
+ bug_if_nullptr (ret);
+ bug_if_nullptr (ret_len);
+
+ *ret = anydata->meta1;
+ *ret_len = anydata->meta1_len;
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_anydata_getref_meta2 (const mcpc_anydata_t *anydata, const char8_t **ret, size_t *ret_len)
+{
+ bug_if_nullptr (anydata);
+ bug_if_nullptr (ret);
+ bug_if_nullptr (ret_len);
+
+ *ret = anydata->meta2;
+ *ret_len = anydata->meta2_len;
+
+ return MCPC_EC_0;
+}
+
+/* = = = = = = = = = = = = = = = = anypool = = = = = = = = = = = = = = = = */
+
+mcpc_anypool_t *
+mcpc_anypool_new ()
+{
+ mcpc_anypool_t *hp_anypool = mcpc_calloc (sizeof (mcpc_anypool_t));
+ return hp_anypool;
+}
+
+void
+mcpc_anypool_free (mcpc_anypool_t *anypool)
+{
+ bug_if (anypool == nullptr);
+ for (size_t i = 0; i < anypool->len; i++)
+ {
+ mcpc_anydata_t *cur_data = (mcpc_anydata_t *) anypool->head + i;
+ mcpc_anydata_mbfree (cur_data);
+ }
+ if (anypool->cap > 0 && anypool->head != nullptr)
+ {
+ free ((void *) anypool->head);
+ }
+ free (anypool);
+}
+
+
+void
+mcpc_anypool_set_cap (mcpc_anypool_t *pool, size_t newcap)
+{
+ size_t newsz = newcap * sizeof (mcpc_anydata_t);
+ pool->head = mcpc_realloc ((void *) pool->head, newsz); // TODO use wrapper
+ pool->cap = newcap;
+}
+
+
+void
+mcpc_anypool_copy (mcpc_anypool_t *dest_pool, const mcpc_anypool_t *src_pool)
+{
+ bug_if (dest_pool == nullptr);
+ bug_if (src_pool == nullptr);
+
+ // make sure inited. this also means dest's content is insignificant
+ memset (dest_pool, 0, sizeof (mcpc_anypool_t));
+
+ mcpc_anypool_set_cap (dest_pool, src_pool->cap);
+
+ for (size_t i = 0; i < src_pool->len; i++) // TODO ckdadd
+ mcpc_anydata_copy ((mcpc_anydata_t *) dest_pool->head + i, src_pool->head + i);
+
+ dest_pool->len = src_pool->len;
+}
+
+void
+mcpc_anypool_grow (mcpc_anypool_t *pool)
+{
+ size_t newcap = pool->cap + mcpc_anypool_gamt;
+ mcpc_anypool_set_cap (pool, newcap);
+}
+
+void
+mcpc_anypool_addcpy (mcpc_anypool_t *anypool, const mcpc_anydata_t *tmp_data)
+{
+ bug_if (anypool == nullptr);
+ bug_if (tmp_data == nullptr);
+
+ if (anypool->len + 1 > anypool->cap)
+ {
+ mcpc_anypool_grow (anypool);
+ }
+ bug_if (anypool->len + 1 > anypool->cap);
+
+ mcpc_anydata_t *newdata = (mcpc_anydata_t *) (anypool->head + anypool->len);
+ mcpc_anydata_copy (newdata, tmp_data);
+ anypool->len += 1;
+}
+
+void
+mcpc_anypool_addfre (mcpc_anypool_t *anypool, mcpc_anydata_t *tmp_data)
+{
+ mcpc_anypool_addcpy (anypool, tmp_data);
+ mcpc_anydata_free (tmp_data);
+}
+
+const mcpc_anydata_t *
+mcpc_anypool_get (const mcpc_anypool_t *anypool, size_t idx)
+{
+ bug_if (anypool == nullptr);
+ if (idx >= anypool->len)
+ return nullptr;
+ return anypool->head + idx;
+}
+
+const mcpc_anydata_t *
+mcpc_anypool_getbymeta1 (const mcpc_anypool_t *anypool, const char8_t *meta1, size_t meta1_len)
+{
+ bug_if (anypool == nullptr);
+
+ if (meta1 == nullptr || meta1_len == 0)
+ return nullptr;
+
+ for (size_t i = 0; i < anypool->len; i++) // TODO: ckdadd
+ {
+ const mcpc_anydata_t *cur_data = anypool->head + i;
+ if (cur_data->meta1_len > 0)
+ {
+ size_t cmpsz = min_size (meta1_len, (size_t) cur_data->meta1_len);
+ if (0 == u8strncmp (cur_data->meta1, meta1, cmpsz))
+ return cur_data;
+ }
+ }
+
+ return nullptr;
+}
+
+const mcpc_anydata_t *
+mcpc_anypool_getbymeta1nt (const mcpc_anypool_t *pool, const char8_t *meta1nt)
+{
+ size_t meta1nt_len = u8strlen (meta1nt);
+ return mcpc_anypool_getbymeta1 (pool, meta1nt, meta1nt_len);
+}
diff --git a/third_party/mcpc/src/anydata.h b/third_party/mcpc/src/anydata.h
new file mode 100644
index 0000000..1188e05
--- /dev/null
+++ b/third_party/mcpc/src/anydata.h
@@ -0,0 +1,225 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_anydata
+#define h_anydata
+
+#include <stddef.h>
+
+#include "ext_stdint.h"
+
+#include <mcpc/_c23_keywords.h>
+#include <mcpc/_c23_uchar.h>
+
+#include <mcpc/anydata.h>
+#include <mcpc/errcode.h>
+
+// ---------------------------- anydata ----------------------------
+
+#ifdef MCPC_C23GIVUP_FIXENUM
+enum mcpc_anydata_flags
+#else
+enum mcpc_anydata_flags:u16_t
+#endif
+{
+ ANYDATA_FL_REQUIRED = 1 << 0,
+ ANYDATA_FL_TOOL = 1 << 1,
+ ANYDATA_FL_TOOLPROP = 1 << 2,
+ ANYDATA_FL_RSC = 1 << 3,
+ ANYDATA_FL_PRMPT = 1 << 4,
+ ANYDATA_FL_PRMPTARG = 1 << 5,
+ ANYDATA_FL_USRCBRES_TOOLCALL = 1 << 6,
+ ANYDATA_FL_USRCBRES_TOOLCALL_TXTCTN = 1 << 7,
+ ANYDATA_FL_USRCBRES_TOOLCALL_IMGCTN = 1 << 8,
+ ANYDATA_FL_USRCBRES_PRMPTCALL = 1 << 9,
+ ANYDATA_FL_USRCBRES_PRMPTCALL_ASSIST = 1 << 10,
+ ANYDATA_FL_COMPLT = 1 << 11,
+ ANYDATA_FL_PRMPTARGHINT = 1 << 12,
+ ANYDATA_FL_PRESENT = 1 << 13,
+};
+typedef enum mcpc_anydata_flags mcpc_anydata_flags_t;
+
+struct mcpc_anydata
+{
+ u8_t meta1_len;
+ mcpc_mime_t data_mime;
+ mcpc_anytype_t data_typ;
+ mcpc_anydata_flags_t flags;
+ u16_t meta2_len;
+ size_t data_len;
+ const char8_t *meta1;
+ const char8_t *meta2;
+ mcpc_anypool_t *chd_pool;
+ union
+ {
+ uintmax_t data_intlk;
+ uintptr_t *data_arrlk;
+ };
+};
+
+mcpc_anydata_t *mcpc_anydata_new ();
+
+mcpc_anydata_t *mcpc_anydata_new_typ (mcpc_anytype_t typ);
+
+mcpc_anydata_t *mcpc_anydata_new_meta1_meta2 (const char8_t * meta1,
+ const size_t meta1_len, const char8_t * meta2, const size_t meta2_len);
+
+mcpc_anydata_t *mcpc_anydata_new_meta1nt_meta2nt (const char8_t * meta1nt, const char8_t * meta2nt);
+
+mcpc_anydata_t *mcpc_anydata_new_typ_meta1 (mcpc_anytype_t typ, const char8_t * meta1, const size_t meta1_len);
+
+mcpc_anydata_t *mcpc_anydata_new_typ_meta1_meta2 (mcpc_anytype_t typ,
+ const char8_t * meta1,
+ const size_t meta1_len, const char8_t * meta2,
+ const size_t meta2_len);
+
+mcpc_anydata_t *mcpc_anydata_new_typ_meta1nt_meta2nt (mcpc_anytype_t typ,
+ const char8_t * meta1nt, const char8_t * meta2nt);
+
+void mcpc_anydata_free (mcpc_anydata_t * data);
+
+void mcpc_anydata_copy (mcpc_anydata_t * dest_data, const mcpc_anydata_t * src_data);
+
+mcpc_errcode_t mcpc_anydata_set_meta1nt (mcpc_anydata_t * anydata, const char8_t * meta1);
+
+mcpc_errcode_t mcpc_anydata_set_i8 (mcpc_anydata_t * anydata, i8_t val);
+
+mcpc_errcode_t mcpc_anydata_set_u8 (mcpc_anydata_t * anydata, u8_t val);
+
+mcpc_errcode_t mcpc_anydata_set_i16 (mcpc_anydata_t * anydata, i16_t val);
+
+mcpc_errcode_t mcpc_anydata_set_u16 (mcpc_anydata_t * anydata, u16_t val);
+
+mcpc_errcode_t mcpc_anydata_set_i32 (mcpc_anydata_t * anydata, i32_t val);
+
+mcpc_errcode_t mcpc_anydata_set_u32 (mcpc_anydata_t * anydata, u32_t val);
+
+mcpc_errcode_t mcpc_anydata_set_i64 (mcpc_anydata_t * anydata, i64_t val);
+
+mcpc_errcode_t mcpc_anydata_set_u64 (mcpc_anydata_t * anydata, u64_t val);
+
+mcpc_errcode_t mcpc_anydata_set_func (mcpc_anydata_t * anydata, void *val);
+
+mcpc_errcode_t mcpc_anydata_get_i8 (const mcpc_anydata_t * anydata, i8_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_u8 (const mcpc_anydata_t * anydata, u8_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_i16 (const mcpc_anydata_t * anydata, i16_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_u16 (const mcpc_anydata_t * anydata, u16_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_i32 (const mcpc_anydata_t * anydata, i32_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_u32 (const mcpc_anydata_t * anydata, u32_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_i64 (const mcpc_anydata_t * anydata, i64_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_u64 (const mcpc_anydata_t * anydata, u64_t * ret);
+
+mcpc_errcode_t mcpc_anydata_get_func (const mcpc_anydata_t * anydata, void *ret);
+
+mcpc_errcode_t mcpc_anydata_set_i8s (mcpc_anydata_t * anydata, const i8_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_u8s (mcpc_anydata_t * anydata, const u8_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_i16s (mcpc_anydata_t * anydata, const i16_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_u16s (mcpc_anydata_t * anydata, const u16_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_i32s (mcpc_anydata_t * anydata, const i32_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_u32s (mcpc_anydata_t * anydata, const u32_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_i64s (mcpc_anydata_t * anydata, const i64_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_u64s (mcpc_anydata_t * anydata, const u64_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_set_u8str (mcpc_anydata_t * anydata, const char8_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_anydata_get_i8s (const mcpc_anydata_t * anydata, i8_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_u8s (const mcpc_anydata_t * anydata, u8_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_i16s (const mcpc_anydata_t * anydata, i16_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_u16s (const mcpc_anydata_t * anydata, u16_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_i32s (const mcpc_anydata_t * anydata, i32_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_u32s (const mcpc_anydata_t * anydata, u32_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_i64s (const mcpc_anydata_t * anydata, i64_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_u64s (const mcpc_anydata_t * anydata, u64_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_get_u8str (const mcpc_anydata_t * anydata, char8_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_getref_u8str (const mcpc_anydata_t * anydata, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_getref_meta1 (const mcpc_anydata_t * anydata, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_anydata_getref_meta2 (const mcpc_anydata_t * anydata, const char8_t ** ret, size_t *ret_len);
+
+const mcpc_anydata_t *mcpc_anydata_get_child (const mcpc_anydata_t * anydata, size_t idx);
+
+void mcpc_anydata_add_child (mcpc_anydata_t * data, mcpc_anydata_t * child);
+
+void mcpc_anydata_addcpy_child (mcpc_anydata_t * data, const mcpc_anydata_t * child);
+
+// ---------------------------- anypool ----------------------------
+
+#ifdef MCPC_C23GIVUP_FIXENUM
+enum mcpc_anypool_flags
+#else
+enum mcpc_anypool_flags:u16_t
+#endif
+{
+ ANYPOOL_FL_USRCBRES_TOOLCALL = 1 << 0,
+ ANYPOOL_FL_USRCBRES_TOOLCALL_ERROR = 1 << 1,
+ ANYPOOL_FL_USRCBRES_PRMPTCALL = 1 << 2,
+};
+typedef enum mcpc_anypool_flags mcpc_anypool_flags_t;
+
+struct mcpc_anypool
+{
+ mcpc_anypool_flags_t flags;
+ size_t cap;
+ size_t len;
+ const mcpc_anydata_t *head;
+};
+
+const mcpc_anydata_t *mcpc_anypool_get (const mcpc_anypool_t * anypool, size_t idx);
+
+const mcpc_anydata_t *mcpc_anypool_getbymeta1 (const mcpc_anypool_t * anypool, const char8_t * meta1, size_t meta1_len);
+
+const mcpc_anydata_t *mcpc_anypool_getbymeta1nt (const mcpc_anypool_t * pool, const char8_t * meta1nt);
+
+mcpc_anypool_t *mcpc_anypool_new ();
+
+void mcpc_anypool_free (mcpc_anypool_t * anypool);
+
+void mcpc_anypool_copy (mcpc_anypool_t * dest_pool, const mcpc_anypool_t * src_pool);
+
+void mcpc_anypool_addfre (mcpc_anypool_t * anypool, mcpc_anydata_t * tmp_data);
+
+void mcpc_anypool_addcpy (mcpc_anypool_t * anypool, const mcpc_anydata_t * tmp_data);
+
+#endif
diff --git a/third_party/mcpc/src/complt.c b/third_party/mcpc/src/complt.c
new file mode 100644
index 0000000..730d53b
--- /dev/null
+++ b/third_party/mcpc/src/complt.c
@@ -0,0 +1,162 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "complt.h"
+
+#include "anydata.h"
+#include "ext_string.h"
+
+
+mcpc_complt_t *
+mcpc_complt_new ()
+{
+ mcpc_anydata_t *hp = mcpc_anydata_new ();
+
+ hp->flags |= ANYDATA_FL_COMPLT;
+
+ return hp;
+}
+
+mcpc_errcode_t
+mcpc_complt_add_compltcandi_u8str (mcpc_complt_t *complt, const char8_t *u8str, size_t u8str_len)
+{
+ bug_if_nullptr (complt);
+ bug_if_nullptr (u8str);
+ mcpc_anydata_t *chd = mcpc_anydata_new ();
+ bug_if_nz (mcpc_anydata_set_u8str (chd, u8str, u8str_len));
+ if (complt->chd_pool == nullptr)
+ complt->chd_pool = mcpc_anypool_new ();
+ (mcpc_anypool_addfre (complt->chd_pool, chd));
+ return MCPC_EC_0;
+}
+
+// provide completions for specific prompt arg, completing its hint, (currently hint is anydata with merely u8str data)
+mcpc_complt_t *
+mcpc_complt_new_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *argname, size_t argname_len, const char8_t *argval,
+ size_t argval_len)
+{
+ mcpc_complt_t *complt = mcpc_complt_new ();
+
+ const mcpc_prmptarg_t *prmptarg = mcpc_prmpt_getbyname_prmptarg (prmpt, argname, argname_len);
+ if (prmptarg == nullptr)
+ return complt;
+
+ bool match_any = argval_len == 0;
+
+ const mcpc_prmptarghint_t *cur_candi = nullptr;
+ while ((cur_candi = mcpc_prmptarg_get_next_prmptarghint (prmptarg)))
+ {
+ const char8_t *cur_val;
+ size_t cur_val_len;
+ bug_if_nz (mcpc_prmptarghint_getref_u8str (cur_candi, &cur_val, &cur_val_len));
+
+ if (match_any)
+ bug_if_nz (mcpc_complt_add_compltcandi_u8str (complt, cur_val, cur_val_len));
+ else
+ {
+ size_t cmpsz = min_size (cur_val_len, argval_len);
+ if (0 == u8strncmp (argval, cur_val, cmpsz))
+ {
+ bug_if_nz (mcpc_complt_add_compltcandi_u8str (complt, cur_val, cur_val_len));
+ }
+ }
+ }
+
+ return complt;
+}
+
+// provide completions for specific rsc, completing uri
+mcpc_complt_t *
+mcpc_complt_new_rsc (const mcpc_rscpool_t *rscpool, const char8_t *uriparts, size_t uriparts_len)
+{
+ mcpc_complt_t *complt = mcpc_complt_new ();
+
+ bool match_any = uriparts_len == 0;
+
+ const mcpc_rsc_t *cur_rsc = nullptr;
+ while ((cur_rsc = mcpc_rscpool_get_next (rscpool)))
+ {
+ const char8_t *cur_uri;
+ size_t cur_uri_len;
+ bug_if_nz (mcpc_rsc_getref_uri (cur_rsc, &cur_uri, &cur_uri_len));
+
+ if (match_any)
+ bug_if_nz (mcpc_complt_add_compltcandi_u8str (complt, cur_uri, cur_uri_len));
+ else if (uriparts_len <= cur_uri_len)
+ {
+ size_t cmpsz = uriparts_len;
+ if (0 == u8strncmp (cur_uri, uriparts, cmpsz))
+ bug_if_nz (mcpc_complt_add_compltcandi_u8str (complt, cur_uri, cur_uri_len));
+ }
+ }
+
+ return complt;
+}
+
+void
+mcpc_complt_free (mcpc_complt_t *complt)
+{
+ mcpc_anydata_free (complt);
+}
+
+mcpc_errcode_t
+mcpc_complt_get_compltcandi_len (const mcpc_complt_t *complt, size_t *ret)
+{
+ bug_if_nullptr (complt);
+ if (complt->chd_pool == nullptr)
+ *ret = 0;
+ else
+ *ret = complt->chd_pool->len;
+ return MCPC_EC_0;
+}
+
+const mcpc_compltcand_t *
+mcpc_complt_get_next_compltcand (const mcpc_complt_t *complt)
+{
+ bug_if_nullptr (complt);
+
+ static size_t idx = 0;
+
+ const mcpc_anypool_t *pool = complt->chd_pool;
+ if (pool == nullptr)
+ return nullptr;
+
+ const size_t pool_len = pool->len;
+ if (pool_len == 0)
+ return nullptr;
+
+ if (idx == pool_len) // TODO MT-SAFE
+ {
+ idx = 0;
+ return nullptr;
+ }
+
+ const mcpc_anydata_t *ret = pool->head + idx;
+ idx += 1;
+
+ return ret;
+}
+
+mcpc_errcode_t
+mcpc_compltcand_getref_u8str (const mcpc_compltcand_t *compltcand, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_u8str (compltcand, ret, ret_len);
+}
diff --git a/third_party/mcpc/src/complt.h b/third_party/mcpc/src/complt.h
new file mode 100644
index 0000000..c11dc3f
--- /dev/null
+++ b/third_party/mcpc/src/complt.h
@@ -0,0 +1,60 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_complt
+#define h_complt
+
+// not supposed to be used by lib users
+
+#include <mcpc/_c23_keywords.h>
+#include <mcpc/_c23_uchar.h>
+
+#include "ext_string.h"
+
+#include <mcpc/anydata.h>
+
+#include "errcode.h"
+
+#include "prmpt.h"
+#include "rsc.h"
+
+
+typedef mcpc_anydata_t mcpc_complt_t;
+typedef mcpc_anydata_t mcpc_compltcand_t;
+
+mcpc_complt_t *mcpc_complt_new_prmptarg (const mcpc_prmpt_t * prmpt, const char8_t * argname, size_t argname_len,
+ const char8_t * argval, size_t argval_len);
+
+mcpc_complt_t *mcpc_complt_new_rsc (const mcpc_rscpool_t * rscpool, const char8_t * uri, size_t uri_len);
+
+void mcpc_complt_free (mcpc_complt_t * complt);
+
+const mcpc_compltcand_t *mcpc_complt_get_next_compltcand (const mcpc_complt_t * complt);
+
+mcpc_errcode_t mcpc_complt_get_compltcandi_len (const mcpc_complt_t * complt, size_t *ret);
+
+
+mcpc_errcode_t
+mcpc_compltcand_getref_u8str (const mcpc_compltcand_t * compltcand, const char8_t ** ret, size_t *ret_len);
+
+
+
+#endif
diff --git a/third_party/mcpc/src/errcode.c b/third_party/mcpc/src/errcode.c
new file mode 100644
index 0000000..9fa5ddc
--- /dev/null
+++ b/third_party/mcpc/src/errcode.c
@@ -0,0 +1,39 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "errcode.h"
+
+#include <stdlib.h>
+// #include <stdio.h>
+
+mcpc_errcode_t mcpc_ec = MCPC_EC_0;
+
+void mcpc_assert(const bool cond, const mcpc_errcode_t ec)
+{
+ if (cond)
+ {
+ return;
+ }
+ mcpc_ec = ec;
+ abort();
+ // fprintf(stderr,"!!!\n");
+ // exit((int)ec);
+}
diff --git a/third_party/mcpc/src/errcode.h b/third_party/mcpc/src/errcode.h
new file mode 100644
index 0000000..6cd1d88
--- /dev/null
+++ b/third_party/mcpc/src/errcode.h
@@ -0,0 +1,74 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_errcode
+#define h_errcode
+
+
+#include <mcpc/errcode.h>
+#include <mcpc/_c23_keywords.h>
+
+
+
+// [[maybe_unused]] static mcpc_errcode_t mcpc_ec = MCPC_EC_CONNPOOL_NOTADD;
+// useless: after abort(), p mcpc_ec is always default value
+extern mcpc_errcode_t mcpc_ec;
+
+// [[noreturn]] ???
+void
+mcpc_assert(const bool cond, const mcpc_errcode_t ec);
+
+
+static inline void bug_if_nullptr(const void *ptr)
+{
+ if (ptr == nullptr)
+ {
+ mcpc_assert(false, MCPC_EC_BUG);
+ }
+}
+
+static inline void bug_if_zero(const unsigned long long ptr)
+{
+ if (ptr == 0)
+ {
+ mcpc_assert(false, MCPC_EC_BUG);
+ }
+}
+
+static inline void bug_if_nz(const unsigned long long ptr)
+{
+ if (ptr != 0)
+ {
+ mcpc_assert(false, MCPC_EC_BUG);
+ }
+}
+
+
+static inline void bug_if(bool f)
+{
+ if (f)
+ {
+ mcpc_assert(false, MCPC_EC_BUG);
+ }
+}
+
+
+#endif
diff --git a/third_party/mcpc/src/ext_stdint.h b/third_party/mcpc/src/ext_stdint.h
new file mode 100644
index 0000000..bc47e35
--- /dev/null
+++ b/third_party/mcpc/src/ext_stdint.h
@@ -0,0 +1,54 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_ext_stdint
+#define h_ext_stdint
+
+#include <stdint.h>
+#include <stddef.h>
+
+typedef int8_t i8_t;
+typedef uint8_t u8_t;
+typedef int16_t i16_t;
+typedef uint16_t u16_t;
+typedef int32_t i32_t;
+typedef uint32_t u32_t;
+typedef int64_t i64_t;
+typedef uint64_t u64_t;
+
+static inline i32_t
+min_i32 (i32_t a, i32_t b)
+{
+ if (a < b)
+ return a;
+ else
+ return b;
+}
+
+static inline size_t
+min_size (size_t a, size_t b)
+{
+ if (b < a)
+ return b;
+ return a;
+}
+
+#endif
diff --git a/third_party/mcpc/src/ext_string.h b/third_party/mcpc/src/ext_string.h
new file mode 100644
index 0000000..87cc198
--- /dev/null
+++ b/third_party/mcpc/src/ext_string.h
@@ -0,0 +1,75 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h__string
+#define h__string
+
+#include <string.h>
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <mcpc/_c23_keywords.h>
+#include <mcpc/_c23_uchar.h>
+
+#define u8c1(a) u8##"" a
+#define u8c2(a, b) u8##"" a b
+#define u8c3(a, b, c) u8##"" a b c
+#define u8c4(a, b, c, d) u8##"" a b c d
+#define u8c5(a, b, c, d, e) u8##"" a b c d e
+#define u8c6(a, b, c, d, e, f) u8##"" a b c d e f
+#define u8c7(a, b, c, d, e, f, g) u8##"" a b c d e f g
+
+inline static bool
+u8streq(const char8_t* a, const char8_t* b)
+{
+ const char* const aa = (const char* const)a;
+ const char* const bb = (const char* const)b;
+ return strcmp(aa, bb) == 0;
+}
+
+inline static size_t
+u8strlen(const char8_t* u8s)
+{
+ const char* s = (const char*)u8s;
+ return strlen(s);
+}
+
+static inline int
+u8strcmp (const char8_t *s1, const char8_t *s2)
+{
+ return strcmp ((const char *) s1, (const char *) s2);
+}
+
+static inline int
+u8strncmp (const char8_t *s1, const char8_t *s2, size_t n)
+{
+ return strncmp ((const char *) s1, (const char *) s2, n);
+}
+
+static inline bool
+beg_with_alph(const char8_t* const u8s)
+{
+ const char* const s = (const char* const)u8s;
+ return s[0] == '/' || isalpha((int)s[0]);
+}
+
+#endif
diff --git a/third_party/mcpc/src/log.c b/third_party/mcpc/src/log.c
new file mode 100644
index 0000000..ebf9879
--- /dev/null
+++ b/third_party/mcpc/src/log.c
@@ -0,0 +1,46 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "log.h"
+
+LogLevel glv = Debug;
+
+void
+lvlog (LogLevel lv, const char *meta, FILE *stream, const char *format, ...)
+{
+ if (lv > glv)
+ return;
+
+ char logind = 'E';
+ if (lv == Info)
+ logind = 'I';
+ else if (lv == Debug)
+ logind = 'D';
+
+ va_list args;
+ va_start (args, format);
+ fprintf (stream, "[%c] %s ", logind, meta);
+ vfprintf (stream, format, args);
+ fprintf (stream, "\n");
+ fflush (stream);
+ fclose (stream);
+ va_end (args);
+}
diff --git a/third_party/mcpc/src/log.h b/third_party/mcpc/src/log.h
new file mode 100644
index 0000000..ae9eb72
--- /dev/null
+++ b/third_party/mcpc/src/log.h
@@ -0,0 +1,60 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_log
+#define h_log
+
+#include <stdarg.h>
+#include <stdio.h>
+
+typedef enum
+{
+ Error = 1,
+ Info,
+ Debug,
+} LogLevel;
+
+extern LogLevel glv; // TODO: thread-safety
+
+#ifdef MCPC_DBG
+#define tulog_d(...) \
+ do \
+ { \
+ char meta[100]; \
+ sprintf(meta, "%s,%d", __TU__, __LINE__); \
+ lvlog(Debug, meta, stderr __VA_OPT__(, ) __VA_ARGS__); \
+ } while (false)
+#define tuflog_d(...) \
+ do \
+ { \
+ FILE *logf = fopen("/tmp/mcpc.log", "a"); \
+ char meta[100]; \
+ sprintf(meta, "%s,%d", __TU__, __LINE__); \
+ lvlog(Debug, meta, logf __VA_OPT__(, ) __VA_ARGS__); \
+ } while (false)
+#else
+#define tulog_d(...) ;
+#define tuflog_d(...) ;
+#endif
+
+void lvlog(LogLevel lv, const char *meta, FILE *stream, const char *format, ...);
+
+#endif
diff --git a/third_party/mcpc/src/mcpc.pc b/third_party/mcpc/src/mcpc.pc
new file mode 100644
index 0000000..4dbd624
--- /dev/null
+++ b/third_party/mcpc/src/mcpc.pc
@@ -0,0 +1,10 @@
+prefix=/usr/local
+libdir=${prefix}/lib
+includedir=${prefix}/include
+
+Name: mcpc
+Description: A library for Model Context Protocol(MCP)
+URL: https://github.com/micl2e2/mcpc
+Version: 0.1.0
+Libs: -L${libdir} -lmcpc
+Cflags: -I${includedir}
diff --git a/third_party/mcpc/src/mjson.c b/third_party/mcpc/src/mjson.c
new file mode 100644
index 0000000..66123da
--- /dev/null
+++ b/third_party/mcpc/src/mjson.c
@@ -0,0 +1,1372 @@
+// Copyright (c) 2018-2020 Cesanta Software Limited
+// All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#if is_wincl
+#pragma warning(disable : 5040)
+#endif
+
+#include <float.h>
+#include <math.h>
+
+#include "mjson.h"
+
+#if is_win || is_mingw
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER < 1700
+#define va_copy(x, y) (x) = (y)
+#define isinf(x) !_finite(x)
+#define isnan(x) _isnan(x)
+#endif
+
+static double
+mystrtod(const char* str, const char** end);
+
+static int
+mjson_esc(int c, int esc)
+{
+ const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
+ for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
+ if (*p == c)
+ return esc ? esc2[p - esc1] : esc1[p - esc2];
+ }
+ return 0;
+}
+
+static int
+mjson_escape(int c)
+{
+ return mjson_esc(c, 1);
+}
+
+static int
+mjson_pass_string(const char* s, int len)
+{
+ int i;
+ for (i = 0; i < len; i++) {
+ if (s[i] == '\\' && i + 1 < len && mjson_escape(s[i + 1])) {
+ i++;
+ } else if (s[i] == '\0') {
+ return MJSON_ERROR_INVALID_INPUT;
+ } else if (s[i] == '"') {
+ return i;
+ }
+ }
+ return MJSON_ERROR_INVALID_INPUT;
+}
+
+int
+mjson(const char* s, int len, mjson_cb_t cb, void* ud)
+{
+ enum
+ {
+ S_VALUE,
+ S_KEY,
+ S_COLON,
+ S_COMMA_OR_EOO
+ } expecting = S_VALUE;
+ unsigned char nesting[MJSON_MAX_DEPTH];
+ int i, depth = 0;
+#define MJSONCALL(ev) \
+ if (cb != NULL && cb(ev, s, start, i - start + 1, ud)) \
+ return i + 1;
+
+// In the ascii table, the distance between `[` and `]` is 2.
+// Ditto for `{` and `}`. Hence +2 in the code below.
+#define MJSONEOO() \
+ do { \
+ if (c != nesting[depth - 1] + 2) \
+ return MJSON_ERROR_INVALID_INPUT; \
+ depth--; \
+ if (depth == 0) { \
+ MJSONCALL(tok); \
+ return i + 1; \
+ } \
+ } while (0)
+
+ for (i = 0; i < len; i++) {
+ int start = i;
+ unsigned char c = ((const unsigned char*)s)[i];
+ int tok = c;
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ continue;
+ // printf("- %c [%.*s] %d %d\n", c, i, s, depth, expecting);
+ switch (expecting) {
+ case S_VALUE:
+ if (c == '{') {
+ if (depth >= (int)sizeof(nesting))
+ return MJSON_ERROR_TOO_DEEP;
+ nesting[depth++] = c;
+ expecting = S_KEY;
+ break;
+ } else if (c == '[') {
+ if (depth >= (int)sizeof(nesting))
+ return MJSON_ERROR_TOO_DEEP;
+ nesting[depth++] = c;
+ break;
+ } else if (c == ']' && depth > 0) { // Empty array
+ MJSONEOO();
+ } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
+ i += 3;
+ tok = MJSON_TOK_TRUE;
+ } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
+ i += 3;
+ tok = MJSON_TOK_NULL;
+ } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
+ i += 4;
+ tok = MJSON_TOK_FALSE;
+ } else if (c == '-' || ((c >= '0' && c <= '9'))) {
+ const char* end = NULL;
+ mystrtod(&s[i], &end);
+ if (end != NULL)
+ i += (int)(end - &s[i] - 1);
+ tok = MJSON_TOK_NUMBER;
+ } else if (c == '"') {
+ int n = mjson_pass_string(&s[i + 1], len - i - 1);
+ if (n < 0)
+ return n;
+ i += n + 1;
+ tok = MJSON_TOK_STRING;
+ } else {
+ return MJSON_ERROR_INVALID_INPUT;
+ }
+ if (depth == 0) {
+ MJSONCALL(tok);
+ return i + 1;
+ }
+ expecting = S_COMMA_OR_EOO;
+ break;
+
+ case S_KEY:
+ if (c == '"') {
+ int n = mjson_pass_string(&s[i + 1], len - i - 1);
+ if (n < 0)
+ return n;
+ i += n + 1;
+ tok = MJSON_TOK_KEY;
+ expecting = S_COLON;
+ } else if (c == '}') { // Empty object
+ MJSONEOO();
+ expecting = S_COMMA_OR_EOO;
+ } else {
+ return MJSON_ERROR_INVALID_INPUT;
+ }
+ break;
+
+ case S_COLON:
+ if (c == ':') {
+ expecting = S_VALUE;
+ } else {
+ return MJSON_ERROR_INVALID_INPUT;
+ }
+ break;
+
+ case S_COMMA_OR_EOO:
+ if (depth <= 0)
+ return MJSON_ERROR_INVALID_INPUT;
+ if (c == ',') {
+ expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
+ } else if (c == ']' || c == '}') {
+ MJSONEOO();
+ } else {
+ return MJSON_ERROR_INVALID_INPUT;
+ }
+ break;
+ }
+ MJSONCALL(tok);
+ }
+ return MJSON_ERROR_INVALID_INPUT;
+}
+
+struct msjon_get_data
+{
+ const char* path; // Lookup json path
+ int pos; // Current path position
+ int d1; // Current depth of traversal
+ int d2; // Expected depth of traversal
+ int i1; // Index in an array
+ int i2; // Expected index in an array
+ int obj; // If the value is array/object, offset where it starts
+ const char** tokptr; // Destination
+ int* toklen; // Destination length
+ int tok; // Returned token
+};
+
+#include <stdio.h>
+
+static int
+plen1(const char* s)
+{
+ int i = 0, n = 0;
+ while (s[i] != '\0' && s[i] != '.' && s[i] != '[')
+ n++, i += s[i] == '\\' ? 2 : 1;
+ // printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n);
+ return n;
+}
+
+static int
+plen2(const char* s)
+{
+ int i = 0, n = 0;
+ while (s[i] != '\0' && s[i] != '.' && s[i] != '[')
+ n++, i += s[i] == '\\' ? 2 : 1;
+ // printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n);
+ return i;
+}
+
+static int
+kcmp(const char* a, const char* b, int n)
+{
+ int i = 0, j = 0, r = 0;
+ for (i = 0, j = 0; j < n; i++, j++) {
+ if (b[i] == '\\')
+ i++;
+ if ((r = a[j] - b[i]) != 0)
+ return r;
+ }
+ // printf("KCMP: a: [%.*s], b:[%.*s] ==> %d\n", n, a, i, b, r);
+ return r;
+}
+
+static int
+mjson_get_cb(int tok, const char* s, int off, int len, void* ud)
+{
+ struct msjon_get_data* d = (struct msjon_get_data*)ud;
+#if 0
+ printf("--> %2x %2d %2d %2d %2d\t %2d %2d\t'%s' '%s' '%s' '%s'\n", tok, d->d1,
+ d->d2, d->i1, d->i2, (int) off, (int) d->pos, s, d->path, s + off,
+ d->path + d->pos);
+#endif
+ if (d->tok != MJSON_TOK_INVALID)
+ return 1; // Found
+ if (tok == '{' || tok == '[') {
+ if (d->d1 < d->d2)
+ d->obj = -1;
+ if (d->d1 == d->d2)
+ d->obj = off;
+ if (d->d1 == d->d2 && tok == '[' && d->path[d->pos] == '[') {
+ d->i1 = 0;
+ d->i2 = (int)mystrtod(&d->path[d->pos + 1], NULL);
+ if (d->i1 == d->i2) {
+ while (d->path[d->pos] && d->path[d->pos] != ']')
+ d->pos++;
+ if (d->path[d->pos] == ']')
+ d->pos++;
+ d->d2++;
+ }
+ }
+ d->d1++;
+ } else if (tok == '}' || tok == ']') {
+ if (tok == ']' && d->d1 == d->d2)
+ d->i1 = 0;
+ d->d1--;
+ // printf("X %s %d %d %d %d %d\n", d->path + d->pos, d->d1, d->d2, d->i1,
+ // d->i2, d->obj);
+ if (!d->path[d->pos] && d->d1 == d->d2 && d->obj != -1) {
+ d->tok = tok - 2;
+ if (d->tokptr)
+ *d->tokptr = s + d->obj;
+ if (d->toklen)
+ *d->toklen = off - d->obj + 1;
+ return 1;
+ }
+ } else if (tok == ',' && d->d1 == d->d2 && d->pos &&
+ d->path[d->pos - 1] == ']') {
+ return 1; // Not found in the current elem array
+ } else if (tok == ',' && d->d1 == d->d2 + 1 && d->path[d->pos] == '[') {
+ // printf("GG '%s' '%s'\n", d->path, &d->path[d->pos]);
+ d->i1++;
+ if (d->i1 == d->i2) {
+ while (d->path[d->pos] && d->path[d->pos] != ']')
+ d->pos++;
+ if (d->path[d->pos] == ']')
+ d->pos++;
+ d->d2++;
+ }
+ } else if (tok == MJSON_TOK_KEY && d->d1 == d->d2 + 1 &&
+ d->path[d->pos] == '.' && s[off] == '"' &&
+ s[off + len - 1] == '"' &&
+ plen1(&d->path[d->pos + 1]) == len - 2 &&
+ kcmp(s + off + 1, &d->path[d->pos + 1], len - 2) == 0) {
+ d->d2++;
+ d->pos += plen2(&d->path[d->pos + 1]) + 1;
+ } else if (tok == MJSON_TOK_KEY && d->d1 == d->d2) {
+ return 1; // Exhausted path, not found
+ } else if (MJSON_TOK_IS_VALUE(tok)) {
+ // printf("T %d %d %d %d %d\n", tok, d->d1, d->d2, d->i1, d->i2);
+ if (d->d1 == d->d2 && d->i1 == d->i2 && !d->path[d->pos]) {
+ d->tok = tok;
+ if (d->tokptr)
+ *d->tokptr = s + off;
+ if (d->toklen)
+ *d->toklen = len;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int
+mjson_find(const char* s, int n, const char* jp, const char** tp, int* tl)
+{
+ struct msjon_get_data data = { jp, 1, 0, 0, 0,
+ 0, -1, tp, tl, MJSON_TOK_INVALID };
+ if (jp[0] != '$')
+ return MJSON_TOK_INVALID;
+ if (mjson(s, n, mjson_get_cb, &data) < 0)
+ return MJSON_TOK_INVALID;
+ return data.tok;
+}
+
+int32_t
+mjson_find8(const char8_t* buf,
+ int32_t len,
+ const char8_t* jp,
+ const char8_t** tp,
+ int32_t* tl)
+{
+ return mjson_find(
+ (const char*)buf, (int)len, (const char*)jp, (const char**)tp, (int*)tl);
+}
+
+int32_t
+mjson_find88(const char* buf,
+ int32_t len,
+ const char8_t* jp,
+ const char8_t** tp,
+ int32_t* tl)
+{
+ return mjson_find(
+ (const char*)buf, (int)len, (const char*)jp, (const char**)tp, (int*)tl);
+}
+
+int
+mjson_get_number(const char* s, int len, const char* path, double* v)
+{
+ const char* p;
+ int tok, n;
+ if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) {
+ if (v != NULL)
+ *v = mystrtod(p, NULL);
+ }
+ return tok == MJSON_TOK_NUMBER ? 1 : 0;
+}
+
+int
+mjson_get_bool(const char* s, int len, const char* path, int* v)
+{
+ int tok = mjson_find(s, len, path, NULL, NULL);
+ if (tok == MJSON_TOK_TRUE && v != NULL)
+ *v = 1;
+ if (tok == MJSON_TOK_FALSE && v != NULL)
+ *v = 0;
+ return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0;
+}
+
+static unsigned char
+unhex(unsigned char c)
+{
+ return (c >= '0' && c <= '9') ? (unsigned char)(c - '0')
+ : (c >= 'A' && c <= 'F') ? (unsigned char)(c - '7')
+ : (unsigned char)(c - 'W');
+}
+
+static unsigned char
+mjson_unhex_nimble(const char* s)
+{
+ const unsigned char* u = (const unsigned char*)s;
+ return (unsigned char)(((unsigned char)(unhex(u[0]) << 4)) | unhex(u[1]));
+}
+
+// n is at most?
+static int
+mjson_unescape(const char* s, int len, char* to, int n)
+{
+ int i, j;
+ for (i = 0, j = 0; i < len && j < n; i++, j++) {
+ if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
+ // \uXXXX escape. We could process a simple one-byte chars
+ // \u00xx from the ASCII range. More complex chars would require
+ // dragging in a UTF8 library, which is too much for us
+ if (s[i + 2] != '0' || s[i + 3] != '0')
+ return -1; // Too much, give up
+ ((unsigned char*)to)[j] = mjson_unhex_nimble(s + i + 4);
+ i += 5;
+ } else if (s[i] == '\\' && i + 1 < len) {
+ int c = mjson_esc(s[i + 1], 0);
+ if (c == 0)
+ return -1;
+ to[j] = (char)(unsigned char)c;
+ i++;
+ } else {
+ to[j] = s[i];
+ }
+ }
+ if (j >= n)
+ return -1;
+ if (n > 0)
+ to[j] = '\0';
+ return j;
+}
+
+int
+mjson_get_string(const char* s, int len, const char* path, char* to, int n)
+{
+ const char* p;
+ int sz;
+ if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING)
+ return -1;
+ return mjson_unescape(p + 1, sz - 2, to, n);
+}
+
+int
+mjson_get_hex(const char* s, int len, const char* x, char* to, int n)
+{
+ const char* p;
+ int i, j, sz;
+ if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING)
+ return -1;
+ for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) {
+ ((unsigned char*)to)[j] = mjson_unhex_nimble(p + i + 1);
+ }
+ if (j < n)
+ to[j] = '\0';
+ return j;
+}
+
+#if MJSON_ENABLE_BASE64
+static unsigned char
+mjson_base64rev(int c)
+{
+ if (c >= 'A' && c <= 'Z') {
+ return (unsigned char)(c - 'A');
+ } else if (c >= 'a' && c <= 'z') {
+ return (unsigned char)(c + 26 - 'a');
+ } else if (c >= '0' && c <= '9') {
+ return (unsigned char)(c + 52 - '0');
+ } else if (c == '+') {
+ return 62;
+ } else if (c == '/') {
+ return 63;
+ } else {
+ return 64;
+ }
+}
+
+int
+mjson_base64_dec(const char* src, int n, char* dst, int dlen)
+{
+ const char* end = src + n;
+ int len = 0;
+ while (src + 3 < end && len < dlen) {
+ unsigned char a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
+ c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
+ dst[len++] = (char)(unsigned char)((a << 2) | (b >> 4));
+ if (src[2] != '=' && len < dlen) {
+ dst[len++] = (char)(unsigned char)((b << 4) | (c >> 2));
+ if (src[3] != '=' && len < dlen) {
+ dst[len++] = (char)(unsigned char)((c << 6) | d);
+ }
+ }
+ src += 4;
+ }
+ if (len < dlen)
+ dst[len] = '\0';
+ return len;
+}
+
+int
+mjson_get_base64(const char* s, int len, const char* path, char* to, int n)
+{
+ const char* p;
+ int sz;
+ if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING)
+ return 0;
+ return mjson_base64_dec(p + 1, sz - 2, to, n);
+}
+#endif // MJSON_ENABLE_BASE64
+
+#if MJSON_ENABLE_NEXT
+struct nextdata
+{
+ int off, len, depth, t, vo, arrayindex;
+ int *koff, *klen, *voff, *vlen, *vtype;
+};
+
+static int
+next_cb(int tok, const char* s, int off, int len, void* ud)
+{
+ struct nextdata* d = (struct nextdata*)ud;
+ // int i;
+ switch (tok) {
+ case '{':
+ case '[':
+ if (d->depth == 0 && tok == '[')
+ d->arrayindex = 0;
+ if (d->depth == 1 && off > d->off) {
+ d->vo = off;
+ d->t = tok == '{' ? MJSON_TOK_OBJECT : MJSON_TOK_ARRAY;
+ if (d->voff)
+ *d->voff = off;
+ if (d->vtype)
+ *d->vtype = d->t;
+ }
+ d->depth++;
+ break;
+ case '}':
+ case ']':
+ d->depth--;
+ if (d->depth == 1 && d->vo) {
+ d->len = off + len;
+ if (d->vlen)
+ *d->vlen = d->len - d->vo;
+ if (d->arrayindex >= 0) {
+ if (d->koff)
+ *d->koff = d->arrayindex; // koff holds array index
+ if (d->klen)
+ *d->klen = 0; // klen holds 0
+ }
+ return 1;
+ }
+ if (d->depth == 1 && d->arrayindex >= 0)
+ d->arrayindex++;
+ break;
+ case ',':
+ case ':':
+ break;
+ case MJSON_TOK_KEY:
+ if (d->depth == 1 && d->off < off) {
+ if (d->koff)
+ *d->koff = off; // And report back to the user
+ if (d->klen)
+ *d->klen = len; // If we have to
+ }
+ break;
+ default:
+ if (d->depth != 1)
+ break;
+ // If we're iterating over the array
+ if (off > d->off) {
+ d->len = off + len;
+ if (d->vlen)
+ *d->vlen = len; // value length
+ if (d->voff)
+ *d->voff = off; // value offset
+ if (d->vtype)
+ *d->vtype = tok; // value type
+ if (d->arrayindex >= 0) {
+ if (d->koff)
+ *d->koff = d->arrayindex; // koff holds array index
+ if (d->klen)
+ *d->klen = 0; // klen holds 0
+ }
+ return 1;
+ }
+ if (d->arrayindex >= 0)
+ d->arrayindex++;
+ break;
+ }
+ (void)s;
+ return 0;
+}
+
+int
+mjson_next(const char* s,
+ int n,
+ int off,
+ int* koff,
+ int* klen,
+ int* voff,
+ int* vlen,
+ int* vtype)
+{
+ struct nextdata d = { off, 0, 0, 0, 0, -1, koff, klen, voff, vlen, vtype };
+ mjson(s, n, next_cb, &d);
+ return d.len;
+}
+#endif
+
+#if MJSON_ENABLE_PRINT
+int
+mjson_print_fixed_buf(const char* ptr, int len, void* fn_data)
+{
+ struct mjson_fixedbuf* fb = (struct mjson_fixedbuf*)fn_data;
+ int i, left = fb->size - fb->len;
+ /* int i, left = fb->size - 1 - fb->len; */
+ if (left < len)
+ len = left;
+ for (i = 0; i < len; i++)
+ fb->ptr[fb->len + i] = ptr[i];
+ fb->len += len;
+ /* fb->ptr[fb->len] = '\0'; */
+ return len;
+}
+
+// This function allocates memory in chunks of size MJSON_DYNBUF_CHUNK
+// to decrease memory fragmentation, when many calls are executed to
+// print e.g. a base64 string or a hex string.
+int
+mjson_print_dynamic_buf(const char* ptr, int len, void* fn_data)
+{
+ char *s, *buf = *(char**)fn_data;
+ size_t curlen = buf == NULL ? 0 : strlen(buf);
+ size_t new_size = curlen + (size_t)len + 1 + MJSON_DYNBUF_CHUNK;
+ new_size -= new_size % MJSON_DYNBUF_CHUNK;
+
+ if ((s = (char*)MJSON_REALLOC(buf, new_size)) == NULL) {
+ return 0;
+ } else {
+ memcpy(s + curlen, ptr, (size_t)len);
+ s[curlen + (size_t)len] = '\0';
+ *(char**)fn_data = s;
+ return len;
+ }
+}
+
+int
+mjson_snprintf(char* buf, size_t len, const char* fmt, ...)
+{
+ va_list ap;
+ struct mjson_fixedbuf fb = { buf, (int)len, 0 };
+ va_start(ap, fmt);
+ mjson_vprintf(mjson_print_fixed_buf, &fb, fmt, &ap);
+ va_end(ap);
+ return fb.len;
+}
+
+char*
+mjson_aprintf(const char* fmt, ...)
+{
+ va_list ap;
+ char* result = NULL;
+ va_start(ap, fmt);
+ mjson_vprintf(mjson_print_dynamic_buf, &result, fmt, &ap);
+ va_end(ap);
+ return result;
+}
+
+int
+mjson_print_null(const char* ptr, int len, void* userdata)
+{
+ (void)ptr;
+ (void)userdata;
+ return len;
+}
+
+int
+mjson_print_buf(mjson_print_fn_t fn, void* fnd, const char* buf, int len)
+{
+ return fn(buf, len, fnd);
+}
+
+int
+mjson_print_long(mjson_print_fn_t fn, void* fnd, long val, int is_signed)
+{
+ unsigned long v = (unsigned long)val, s = 0, n, i;
+ char buf[20], t;
+ if (is_signed && val < 0)
+ buf[s++] = '-', v = (unsigned long)(-val);
+ // This loop prints a number in reverse order. I guess this is because we
+ // write numbers from right to left: least significant digit comes last.
+ // Maybe because we use Arabic numbers, and Arabs write RTL?
+ for (n = 0; v > 0; v /= 10)
+ buf[s + n++] = "0123456789"[v % 10];
+ // Reverse a string
+ for (i = 0; i < n / 2; i++)
+ t = buf[s + i], buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
+ if (val == 0)
+ buf[n++] = '0'; // Handle special case
+ return fn(buf, (int)(s + n), fnd);
+}
+
+int
+mjson_print_int(mjson_print_fn_t fn, void* fnd, int v, int s)
+{
+ return mjson_print_long(fn, fnd, s ? (long)v : (long)(unsigned)v, s);
+}
+
+static int
+addexp(char* buf, int e, int sign)
+{
+ int n = 0;
+ buf[n++] = 'e';
+ buf[n++] = (char)sign;
+ if (e > 400)
+ return 0;
+ if (e < 10)
+ buf[n++] = '0';
+ if (e >= 100)
+ buf[n++] = (char)(e / 100 + '0'), e -= 100 * (e / 100);
+ if (e >= 10)
+ buf[n++] = (char)(e / 10 + '0'), e -= 10 * (e / 10);
+ buf[n++] = (char)(e + '0');
+ return n;
+}
+
+int
+mjson_print_dbl(mjson_print_fn_t fn, void* fnd, double d, int width)
+{
+ char buf[40];
+ int i, s = 0, n = 0, e = 0;
+ double t, mul, saved;
+ if (d == 0.0)
+ return fn("0", 1, fnd);
+ if (isinf(d))
+ return fn(d > 0 ? "inf" : "-inf", d > 0 ? 3 : 4, fnd);
+ if (isnan(d))
+ return fn("nan", 3, fnd);
+ if (d < 0.0)
+ d = -d, buf[s++] = '-';
+
+ // Round
+ saved = d;
+ mul = 1.0;
+ while (d >= 10.0 && d / mul >= 10.0)
+ mul *= 10.0;
+ while (d <= 1.0 && d / mul <= 1.0)
+ mul /= 10.0;
+ for (i = 0, t = mul * 5; i < width; i++)
+ t /= 10.0;
+ d += t;
+ // Calculate exponent, and 'mul' for scientific representation
+ mul = 1.0;
+ while (d >= 10.0 && d / mul >= 10.0)
+ mul *= 10.0, e++;
+ while (d < 1.0 && d / mul < 1.0)
+ mul /= 10.0, e--;
+ // printf(" --> %g %d %g %g\n", saved, e, t, mul);
+
+ if (e >= width) {
+ struct mjson_fixedbuf fb = { buf + s, (int)sizeof(buf) - s, 0 };
+ n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width);
+ // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, fb.len, fb.ptr);
+ n += addexp(buf + s + n, e, '+');
+ return fn(buf, s + n, fnd);
+ } else if (e <= -width) {
+ struct mjson_fixedbuf fb = { buf + s, (int)sizeof(buf) - s, 0 };
+ n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width);
+ // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, fb.len, fb.ptr);
+ n += addexp(buf + s + n, -e, '-');
+ return fn(buf, s + n, fnd);
+ } else {
+ for (i = 0, t = mul; t >= 1.0 && s + n < (int)sizeof(buf); i++) {
+ int ch = (int)(d / t);
+ if (n > 0 || ch > 0)
+ buf[s + n++] = (char)(ch + '0');
+ d -= ch * t;
+ t /= 10.0;
+ }
+ // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
+ if (n == 0)
+ buf[s++] = '0';
+ while (t >= 1.0 && n + s < (int)sizeof(buf))
+ buf[n++] = '0', t /= 10.0;
+ if (s + n < (int)sizeof(buf))
+ buf[n + s++] = '.';
+ // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
+ for (i = 0, t = 0.1; s + n < (int)sizeof(buf) && n < width; i++) {
+ int ch = (int)(d / t);
+ buf[s + n++] = (char)(ch + '0');
+ d -= ch * t;
+ t /= 10.0;
+ }
+ }
+ while (n > 0 && buf[s + n - 1] == '0')
+ n--; // Trim trailing zeros
+ if (n > 0 && buf[s + n - 1] == '.')
+ n--; // Trim trailing dot
+ buf[s + n] = '\0';
+ return fn(buf, s + n, fnd);
+}
+
+int
+mjson_print_str(mjson_print_fn_t fn, void* fnd, const char* s, int len)
+{
+ int i, n = fn("\"", 1, fnd);
+ for (i = 0; i < len; i++) {
+ char c = (char)(unsigned char)mjson_escape(s[i]);
+ if (c) {
+ n += fn("\\", 1, fnd);
+ n += fn(&c, 1, fnd);
+ } else {
+ n += fn(&s[i], 1, fnd);
+ }
+ }
+ return n + fn("\"", 1, fnd);
+}
+
+#if MJSON_ENABLE_BASE64
+int
+mjson_print_b64(mjson_print_fn_t fn, void* fnd, const unsigned char* s, int n)
+{
+ const char* t =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ int i, len = fn("\"", 1, fnd);
+ for (i = 0; i < n; i += 3) {
+ int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0;
+ char buf[4] = { t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '=' };
+ if (i + 1 < n)
+ buf[2] = t[(b & 15) << 2 | (c >> 6)];
+ if (i + 2 < n)
+ buf[3] = t[c & 63];
+ len += fn(buf, sizeof(buf), fnd);
+ }
+ return len + fn("\"", 1, fnd);
+}
+#endif /* MJSON_ENABLE_BASE64 */
+
+int
+mjson_vprintf(mjson_print_fn_t fn, void* fnd, const char* fmt, va_list* ap)
+{
+ int i = 0, n = 0;
+ while (fmt[i] != '\0') {
+ if (fmt[i] == '%') {
+ char fc = fmt[++i];
+ int is_long = 0;
+ if (fc == 'l') {
+ is_long = 1;
+ fc = fmt[i + 1];
+ }
+ if (fc == 'Q') {
+ char* buf = va_arg(*ap, char*);
+ n +=
+ mjson_print_str(fn, fnd, buf ? buf : "", buf ? (int)strlen(buf) : 0);
+ } else if (strncmp(&fmt[i], ".*Q", 3) == 0) {
+ int len = va_arg(*ap, int);
+ char* buf = va_arg(*ap, char*);
+ n += mjson_print_str(fn, fnd, buf, len);
+ i += 2;
+ } else if (fc == 'd' || fc == 'u') {
+ int is_signed = (fc == 'd');
+ if (is_long) {
+ long val = va_arg(*ap, long);
+ n += mjson_print_long(fn, fnd, val, is_signed);
+ i++;
+ } else {
+ int val = va_arg(*ap, int);
+ n += mjson_print_int(fn, fnd, val, is_signed);
+ }
+ } else if (fc == 'B') {
+ const char* s = va_arg(*ap, int) ? "true" : "false";
+ n += mjson_print_buf(fn, fnd, s, (int)strlen(s));
+ } else if (fc == 's') {
+ char* buf = va_arg(*ap, char*);
+ n += mjson_print_buf(fn, fnd, buf, (int)strlen(buf));
+ } else if (strncmp(&fmt[i], ".*s", 3) == 0) {
+ int len = va_arg(*ap, int);
+ char* buf = va_arg(*ap, char*);
+ n += mjson_print_buf(fn, fnd, buf, len);
+ i += 2;
+ } else if (fc == 'g') {
+ n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), 6);
+ } else if (strncmp(&fmt[i], ".*g", 3) == 0) {
+ int width = va_arg(*ap, int);
+ n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), width);
+ i += 2;
+#if MJSON_ENABLE_BASE64
+ } else if (fc == 'V') {
+ int len = va_arg(*ap, int);
+ const char* buf = va_arg(*ap, const char*);
+ n += mjson_print_b64(fn, fnd, (const unsigned char*)buf, len);
+#endif
+ } else if (fc == 'H') {
+ const char* hex = "0123456789abcdef";
+ int j, len = va_arg(*ap, int);
+ const unsigned char* p = va_arg(*ap, const unsigned char*);
+ n += fn("\"", 1, fnd);
+ for (j = 0; j < len; j++) {
+ n += fn(&hex[(p[j] >> 4) & 15], 1, fnd);
+ n += fn(&hex[p[j] & 15], 1, fnd);
+ }
+ n += fn("\"", 1, fnd);
+ } else if (fc == 'M') {
+ mjson_vprint_fn_t vfn = va_arg(*ap, mjson_vprint_fn_t);
+ n += vfn(fn, fnd, ap);
+ }
+ i++;
+ } else {
+ n += mjson_print_buf(fn, fnd, &fmt[i++], 1);
+ }
+ }
+ return n;
+}
+
+int
+mjson_printf(mjson_print_fn_t fn, void* fnd, const char* fmt, ...)
+{
+ va_list ap;
+ int len;
+ va_start(ap, fmt);
+ len = mjson_vprintf(fn, fnd, fmt, &ap);
+ va_end(ap);
+ return len;
+}
+#endif /* MJSON_ENABLE_PRINT */
+
+static int
+is_digit(int c)
+{
+ return c >= '0' && c <= '9';
+}
+
+/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */
+static double
+mystrtod(const char* str, const char** end)
+{
+ double d = 0.0;
+ int sign = 1, n = 0;
+ const char *p = str, *a = str;
+
+ /* decimal part */
+ if (*p == '-') {
+ sign = -1;
+ ++p;
+ } else if (*p == '+') {
+ ++p;
+ }
+ if (is_digit(*p)) {
+ d = (double)(*p++ - '0');
+ while (*p && is_digit(*p)) {
+ d = d * 10.0 + (double)(*p - '0');
+ ++p;
+ ++n;
+ }
+ a = p;
+ } else if (*p != '.') {
+ goto done;
+ }
+ d *= sign;
+
+ /* fraction part */
+ if (*p == '.') {
+ double f = 0.0;
+ double base = 0.1;
+ ++p;
+
+ if (is_digit(*p)) {
+ while (*p && is_digit(*p)) {
+ f += base * (*p - '0');
+ base /= 10.0;
+ ++p;
+ ++n;
+ }
+ }
+ d += f * sign;
+ a = p;
+ }
+
+ /* exponential part */
+ if ((*p == 'E') || (*p == 'e')) {
+ int i, e = 0, neg = 0;
+ p++;
+ if (*p == '-')
+ p++, neg++;
+ if (*p == '+')
+ p++;
+ while (is_digit(*p))
+ e = e * 10 + *p++ - '0';
+ if (neg)
+ e = -e;
+#if 0
+ if (d == 2.2250738585072011 && e == -308) {
+ d = 0.0;
+ a = p;
+ goto done;
+ }
+ if (d == 2.2250738585072012 && e <= -308) {
+ d *= 1.0e-308;
+ a = p;
+ goto done;
+ }
+#endif
+ for (i = 0; i < e; i++)
+ d *= 10;
+ for (i = 0; i < -e; i++)
+ d /= 10;
+ a = p;
+ } else if (p > str && !is_digit(*(p - 1))) {
+ a = str;
+ goto done;
+ }
+
+done:
+ if (end)
+ *end = a;
+ return d;
+}
+
+#if MJSON_ENABLE_MERGE
+int
+mjson_merge(const char* s,
+ int n,
+ const char* s2,
+ int n2,
+ mjson_print_fn_t fn,
+ void* userdata)
+{
+ int koff, klen, voff, vlen, t, t2, k, off = 0, len = 0, comma = 0;
+ if (n < 2)
+ return len;
+ len += fn("{", 1, userdata);
+ while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
+ char* path = (char*)alloca((size_t)klen + 1);
+ const char* val;
+ memcpy(path, "$.", 2);
+ memcpy(path + 2, s + koff + 1, (size_t)(klen - 2));
+ path[klen] = '\0';
+ if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) {
+ if (t2 == MJSON_TOK_NULL)
+ continue; // null deletes the key
+ } else {
+ val = s + voff; // Key is not found in the update. Copy the old value.
+ }
+ if (comma)
+ len += fn(",", 1, userdata);
+ len += fn(s + koff, klen, userdata);
+ len += fn(":", 1, userdata);
+ if (t == MJSON_TOK_OBJECT && t2 == MJSON_TOK_OBJECT) {
+ len += mjson_merge(s + voff, vlen, val, k, fn, userdata);
+ } else {
+ if (t2 != MJSON_TOK_INVALID)
+ vlen = k;
+ len += fn(val, vlen, userdata);
+ }
+ comma = 1;
+ }
+ // Add missing keys
+ off = 0;
+ while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
+ char* path = (char*)alloca((size_t)klen + 1);
+ const char* val;
+ if (t == MJSON_TOK_NULL)
+ continue;
+ memcpy(path, "$.", 2);
+ memcpy(path + 2, s2 + koff + 1, (size_t)(klen - 2));
+ path[klen] = '\0';
+ if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID)
+ continue;
+ if (comma)
+ len += fn(",", 1, userdata);
+ len += fn(s2 + koff, klen, userdata);
+ len += fn(":", 1, userdata);
+ len += fn(s2 + voff, vlen, userdata);
+ comma = 1;
+ }
+ len += fn("}", 1, userdata);
+ return len;
+}
+#endif // MJSON_ENABLE_MERGE
+
+#if MJSON_ENABLE_PRETTY
+struct prettydata
+{
+ int level;
+ int len;
+ int prev;
+ const char* pad;
+ int padlen;
+ mjson_print_fn_t fn;
+ void* userdata;
+};
+
+static int
+pretty_cb(int ev, const char* s, int off, int len, void* ud)
+{
+ struct prettydata* d = (struct prettydata*)ud;
+ int i;
+ switch (ev) {
+ case '{':
+ case '[':
+ d->level++;
+ d->len += d->fn(s + off, len, d->userdata);
+ break;
+ case '}':
+ case ']':
+ d->level--;
+ if (d->prev != '[' && d->prev != '{' && d->padlen > 0) {
+ d->len += d->fn("\n", 1, d->userdata);
+ for (i = 0; i < d->level; i++)
+ d->len += d->fn(d->pad, d->padlen, d->userdata);
+ }
+ d->len += d->fn(s + off, len, d->userdata);
+ break;
+ case ',':
+ d->len += d->fn(s + off, len, d->userdata);
+ if (d->padlen > 0) {
+ d->len += d->fn("\n", 1, d->userdata);
+ for (i = 0; i < d->level; i++)
+ d->len += d->fn(d->pad, d->padlen, d->userdata);
+ }
+ break;
+ case ':':
+ d->len += d->fn(s + off, len, d->userdata);
+ if (d->padlen > 0)
+ d->len += d->fn(" ", 1, d->userdata);
+ break;
+ case MJSON_TOK_KEY:
+ if (d->prev == '{' && d->padlen > 0) {
+ d->len += d->fn("\n", 1, d->userdata);
+ for (i = 0; i < d->level; i++)
+ d->len += d->fn(d->pad, d->padlen, d->userdata);
+ }
+ d->len += d->fn(s + off, len, d->userdata);
+ break;
+ default:
+ if (d->prev == '[' && d->padlen > 0) {
+ d->len += d->fn("\n", 1, d->userdata);
+ for (i = 0; i < d->level; i++)
+ d->len += d->fn(d->pad, d->padlen, d->userdata);
+ }
+ d->len += d->fn(s + off, len, d->userdata);
+ break;
+ }
+ d->prev = ev;
+ return 0;
+}
+
+int
+mjson_pretty(const char* s,
+ int n,
+ const char* pad,
+ mjson_print_fn_t fn,
+ void* userdata)
+{
+ struct prettydata d = { 0, 0, 0, pad, (int)strlen(pad), fn, userdata };
+ if (mjson(s, n, pretty_cb, &d) < 0)
+ return -1;
+ return d.len;
+}
+#endif // MJSON_ENABLE_PRETTY
+
+#if MJSON_ENABLE_RPC
+struct jsonrpc_ctx jsonrpc_default_context;
+
+int
+mjson_globmatch(const char* s1, int n1, const char* s2, int n2)
+{
+ int i = 0, j = 0, ni = 0, nj = 0;
+ while (i < n1 || j < n2) {
+ if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) {
+ i++, j++;
+ } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
+ ni = i, nj = j + 1, i++;
+ } else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
+ i = ni, j = nj;
+ } else {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void
+jsonrpc_return_errorv(struct jsonrpc_request* r,
+ int code,
+ const char* message,
+ const char* data_fmt,
+ va_list* ap)
+{
+ mjson_printf(r->fn, r->fn_data, "{\"jsonrpc\":\"2.0\",\"id\":");
+ if (r->id_len > 0 && r->id != NULL) {
+ mjson_printf(r->fn, r->fn_data, "%.*s", r->id_len, r->id);
+ } else {
+ mjson_printf(r->fn, r->fn_data, "null");
+ }
+ mjson_printf(r->fn,
+ r->fn_data,
+ ",\"error\":{\"code\":%d,\"message\":%Q",
+ code,
+ message == NULL ? "" : message);
+ if (data_fmt != NULL) {
+ mjson_printf(r->fn, r->fn_data, ",\"data\":");
+ mjson_vprintf(r->fn, r->fn_data, data_fmt, ap);
+ }
+ // LF NEWLINE NL
+ mjson_printf(r->fn, r->fn_data, "}}\n");
+ /* mjson_printf(r->fn, r->fn_data, "}}"); */
+}
+
+void
+jsonrpc_return_error(struct jsonrpc_request* r,
+ int code,
+ const char* message,
+ const char* data_fmt,
+ ...)
+{
+ va_list ap;
+ va_start(ap, data_fmt);
+ jsonrpc_return_errorv(r, code, message, data_fmt, &ap);
+ va_end(ap);
+}
+
+void
+jsonrpc_return_successv(struct jsonrpc_request* r,
+ const char* result_fmt,
+ va_list* ap)
+{
+ if (r->id_len == 0)
+ return;
+ mjson_printf(r->fn, r->fn_data, "{\"jsonrpc\":\"2.0\",\"id\":%.*s,\"result\":", r->id_len, r->id);
+ if (result_fmt != NULL) {
+ mjson_vprintf(r->fn, r->fn_data, result_fmt, ap);
+ } else {
+ mjson_printf(r->fn, r->fn_data, "%s", "null");
+ }
+ // LF NEWLINE NL
+ mjson_printf(r->fn, r->fn_data, "}");
+}
+
+
+void
+jsonrpc_return_successv2(struct jsonrpc_request* r, bool add_endl,
+ const char* result_fmt,
+ va_list* ap)
+{
+ if (r->id_len == 0)
+ return; // TODO assert
+ mjson_printf(r->fn, r->fn_data, "{\"jsonrpc\":\"2.0\",\"id\":%.*s,\"result\":", r->id_len, r->id);
+ if (result_fmt != NULL) {
+ mjson_vprintf(r->fn, r->fn_data, result_fmt, ap);
+ } else {
+ mjson_printf(r->fn, r->fn_data, "%s", "null");
+ }
+ // LF NEWLINE NL
+ mjson_printf (r->fn, r->fn_data, "}");
+ if (add_endl)
+ {
+ mjson_printf(r->fn, r->fn_data, "\n");
+ }
+}
+
+void
+jsonrpc_return_success2(struct jsonrpc_request* r, bool add_endl, const char* result_fmt, ...)
+{
+ va_list ap;
+ va_start(ap, result_fmt);
+ jsonrpc_return_successv2(r, add_endl, result_fmt, &ap);
+ va_end(ap);
+}
+
+
+void
+jsonrpc_return_success(struct jsonrpc_request* r, const char* result_fmt, ...)
+{
+ va_list ap;
+ va_start(ap, result_fmt);
+ jsonrpc_return_successv(r, result_fmt, &ap);
+ va_end(ap);
+}
+
+void
+jsonrpc_ctx_process(struct jsonrpc_ctx* ctx,
+ const char* buf,
+ int len,
+ mjson_print_fn_t fn,
+ void* fn_data,
+ void* ud)
+{
+ const char *result = NULL, *error = NULL;
+ int result_sz = 0, error_sz = 0;
+ struct jsonrpc_method* m = NULL;
+ struct jsonrpc_request r = {
+ ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fn_data, ud
+ };
+
+ // Is is a response frame?
+ mjson_find(buf, len, "$.result", &result, &result_sz);
+ if (result == NULL)
+ mjson_find(buf, len, "$.error", &error, &error_sz);
+ if (result_sz > 0 || error_sz > 0) {
+ if (ctx->response_cb)
+ ctx->response_cb(buf, len, ctx->response_cb_data);
+ return;
+ }
+
+ // Method must exist and must be a string
+ if (mjson_find(buf, len, "$.method", &r.method, &r.method_len) !=
+ MJSON_TOK_STRING) {
+ mjson_printf(fn,
+ fn_data,
+ "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n",
+ len,
+ buf);
+ return;
+ }
+
+ // id and params are optional
+ mjson_find(buf, len, "$.id", &r.id, &r.id_len);
+ mjson_find(buf, len, "$.params", &r.params, &r.params_len);
+
+ for (m = ctx->methods; m != NULL; m = m->next) {
+ if (mjson_globmatch(
+ m->method, m->method_sz, r.method + 1, r.method_len - 2) > 0) {
+ if (r.params == NULL)
+ r.params = "";
+ m->cb(&r);
+ break;
+ }
+ }
+ if (m == NULL) {
+ jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL);
+ }
+}
+
+static int
+jsonrpc_print_methods(mjson_print_fn_t fn, void* fn_data, va_list* ap)
+{
+ struct jsonrpc_ctx* ctx = va_arg(*ap, struct jsonrpc_ctx*);
+ struct jsonrpc_method* m;
+ int len = 0;
+ for (m = ctx->methods; m != NULL; m = m->next) {
+ if (m != ctx->methods)
+ len += mjson_print_buf(fn, fn_data, ",", 1);
+ len += mjson_print_str(fn, fn_data, m->method, (int)strlen(m->method));
+ }
+ return len;
+}
+
+void
+jsonrpc_list(struct jsonrpc_request* r)
+{
+ jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->ctx);
+}
+
+void
+jsonrpc_ctx_init(struct jsonrpc_ctx* ctx,
+ mjson_print_fn_t response_cb,
+ void* response_cb_data)
+{
+ ctx->methods = NULL;
+ ctx->response_cb = response_cb;
+ ctx->response_cb_data = response_cb_data;
+}
+
+void
+jsonrpc_init(mjson_print_fn_t response_cb, void* userdata)
+{
+ struct jsonrpc_ctx* ctx = &jsonrpc_default_context;
+ jsonrpc_ctx_init(ctx, response_cb, userdata);
+ jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, jsonrpc_list);
+}
+#endif // MJSON_ENABLE_RPC
diff --git a/third_party/mcpc/src/mjson.h b/third_party/mcpc/src/mjson.h
new file mode 100644
index 0000000..7a562f0
--- /dev/null
+++ b/third_party/mcpc/src/mjson.h
@@ -0,0 +1,296 @@
+// Copyright (c) 2018-2020 Cesanta Software Limited
+// All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef MJSON_H
+#define MJSON_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <mcpc/_c23_uchar.h>
+#include <mcpc/_c23_keywords.h>
+#include <stdint.h>
+
+#ifndef MJSON_ENABLE_PRINT
+#define MJSON_ENABLE_PRINT 1
+#endif
+
+#ifndef MJSON_ENABLE_RPC
+#define MJSON_ENABLE_RPC 1
+#endif
+
+#ifndef MJSON_ENABLE_BASE64
+#define MJSON_ENABLE_BASE64 1
+#endif
+
+#ifndef MJSON_ENABLE_MERGE
+#define MJSON_ENABLE_MERGE 1
+#endif
+
+#ifndef MJSON_ENABLE_PRETTY
+#define MJSON_ENABLE_PRETTY 1
+#endif
+
+#ifndef MJSON_ENABLE_NEXT
+#define MJSON_ENABLE_NEXT 1
+#endif
+
+#ifndef MJSON_RPC_LIST_NAME
+#define MJSON_RPC_LIST_NAME "rpc.list"
+#endif
+
+#ifndef MJSON_DYNBUF_CHUNK
+#define MJSON_DYNBUF_CHUNK 256 // Allocation granularity for print_dynamic_buf
+#endif
+
+#ifndef MJSON_REALLOC
+#define MJSON_REALLOC realloc
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define MJSON_ERROR_INVALID_INPUT (-1)
+#define MJSON_ERROR_TOO_DEEP (-2)
+#define MJSON_TOK_INVALID 0
+#define MJSON_TOK_KEY 1
+#define MJSON_TOK_STRING 11
+#define MJSON_TOK_NUMBER 12
+#define MJSON_TOK_TRUE 13
+#define MJSON_TOK_FALSE 14
+#define MJSON_TOK_NULL 15
+#define MJSON_TOK_ARRAY 91
+#define MJSON_TOK_OBJECT 123
+#define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20)
+
+ typedef int (*mjson_cb_t)(int event,
+ const char* buf,
+ int offset,
+ int len,
+ void* fn_data);
+
+#ifndef MJSON_MAX_DEPTH
+#define MJSON_MAX_DEPTH 20
+#endif
+
+ int mjson(const char* buf, int len, mjson_cb_t cb, void* ud);
+ int mjson_find(const char* buf,
+ int len,
+ const char* jp,
+ const char** tp,
+ int* tl);
+ int32_t mjson_find8(const char8_t* buf,
+ int32_t len,
+ const char8_t* jp,
+ const char8_t** tp,
+ int32_t* tl);
+ int32_t mjson_find88(const char* buf,
+ int32_t len,
+ const char8_t* jp,
+ const char8_t** tp,
+ int32_t* tl);
+ int32_t mjson_find88(const char* buf,
+ int32_t len,
+ const char8_t* jp,
+ const char8_t** tp,
+ int32_t* tl);
+ int mjson_get_number(const char* buf, int len, const char* path, double* v);
+ int mjson_get_bool(const char* buf, int len, const char* path, int* v);
+ int mjson_get_string(const char* buf,
+ int len,
+ const char* path,
+ char* to,
+ int n);
+ int mjson_get_hex(const char* buf,
+ int len,
+ const char* path,
+ char* to,
+ int n);
+
+#if MJSON_ENABLE_NEXT
+ int mjson_next(const char* buf,
+ int len,
+ int offset,
+ int* key_offset,
+ int* key_len,
+ int* val_offset,
+ int* val_len,
+ int* vale_type);
+#endif
+
+#if MJSON_ENABLE_BASE64
+ int mjson_get_base64(const char* buf,
+ int len,
+ const char* path,
+ char* dst,
+ int dst_len);
+ int mjson_base64_dec(const char* src, int src_len, char* dst, int dst_len);
+#endif
+
+#if MJSON_ENABLE_PRINT
+ typedef int (*mjson_print_fn_t)(const char* buf, int len, void* fn_data);
+ typedef int (*mjson_vprint_fn_t)(mjson_print_fn_t fn, void*, va_list*);
+
+ struct mjson_fixedbuf
+ {
+ char* ptr;
+ int size, len;
+ };
+
+ int mjson_printf(mjson_print_fn_t fn, void* fn_data, const char* fmt, ...);
+ int mjson_vprintf(mjson_print_fn_t fn,
+ void* fn_data,
+ const char* fmt,
+ va_list* ap);
+ int mjson_print_str(mjson_print_fn_t fn,
+ void* fn_data,
+ const char* buf,
+ int len);
+ int mjson_print_int(mjson_print_fn_t fn,
+ void* fn_data,
+ int value,
+ int is_signed);
+ int mjson_print_long(mjson_print_fn_t fn,
+ void* fn_data,
+ long value,
+ int is_signed);
+ int mjson_print_buf(mjson_print_fn_t fn,
+ void* fn_data,
+ const char* buf,
+ int len);
+ int mjson_print_dbl(mjson_print_fn_t fn, void* fn_data, double d, int width);
+
+ int mjson_print_null(const char* ptr, int len, void* fn_data);
+ int mjson_print_fixed_buf(const char* ptr, int len, void* fn_data);
+ int mjson_print_dynamic_buf(const char* ptr, int len, void* fn_data);
+
+ int mjson_snprintf(char* buf, size_t len, const char* fmt, ...);
+ char* mjson_aprintf(const char* fmt, ...);
+
+#if MJSON_ENABLE_PRETTY
+ int mjson_pretty(const char* s,
+ int n,
+ const char* pad,
+ mjson_print_fn_t fn,
+ void* fn_data);
+#endif
+
+#if MJSON_ENABLE_MERGE
+ int mjson_merge(const char* s,
+ int n,
+ const char* s2,
+ int n2,
+ mjson_print_fn_t fn,
+ void* fn_data);
+#endif
+
+#endif // MJSON_ENABLE_PRINT
+
+#if MJSON_ENABLE_RPC
+
+ void jsonrpc_init(mjson_print_fn_t response_cb, void* fn_data);
+ int mjson_globmatch(const char* s1, int n1, const char* s2, int n2);
+
+ struct jsonrpc_request
+ {
+ struct jsonrpc_ctx* ctx;
+ const char* frame; // Points to the whole frame
+ int frame_len; // Frame length
+ const char* params; // Points to the "params" in the request frame
+ int params_len; // Length of the "params"
+ const char* id; // Points to the "id" in the request frame
+ int id_len; // Length of the "id"
+ const char* method; // Points to the "method" in the request frame
+ int method_len; // Length of the "method"
+ mjson_print_fn_t fn; // Printer function
+ void* fn_data; // Printer function data
+ void* userdata; // Callback's user data as specified at export time
+ };
+
+ struct jsonrpc_method
+ {
+ const char* method;
+ int method_sz;
+ void (*cb)(struct jsonrpc_request*);
+ struct jsonrpc_method* next;
+ };
+
+ // Main RPC context, stores current request information and a list of
+ // exported RPC methods.
+ struct jsonrpc_ctx
+ {
+ struct jsonrpc_method* methods;
+ mjson_print_fn_t response_cb;
+ void* response_cb_data;
+ };
+
+// Registers function fn under the given name within the given RPC context
+#define jsonrpc_ctx_export(ctx, name, fn) \
+ do { \
+ static struct jsonrpc_method m = { (name), sizeof(name) - 1, (fn), 0 }; \
+ m.next = (ctx)->methods; \
+ (ctx)->methods = &m; \
+ } while (0)
+
+ void jsonrpc_ctx_init(struct jsonrpc_ctx* ctx,
+ mjson_print_fn_t response_cb,
+ void* response_cb_data);
+ void jsonrpc_return_error(struct jsonrpc_request* r,
+ int code,
+ const char* message,
+ const char* data_fmt,
+ ...);
+ void jsonrpc_return_success(struct jsonrpc_request* r,
+ const char* result_fmt,
+ ...);
+ void jsonrpc_return_success2(struct jsonrpc_request* r, bool add_endl,
+ const char* result_fmt,
+ ...);
+ void jsonrpc_ctx_process(struct jsonrpc_ctx* ctx,
+ const char* req,
+ int req_sz,
+ mjson_print_fn_t fn,
+ void* fn_data,
+ void* userdata);
+
+ extern struct jsonrpc_ctx jsonrpc_default_context;
+ extern void jsonrpc_list(struct jsonrpc_request* r);
+
+#define jsonrpc_export(name, fn) \
+ jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn))
+
+#define jsonrpc_process(buf, len, fn, fnd, ud) \
+ jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud))
+
+#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */
+#define JSONRPC_ERROR_INVALID_REQUEST -32600 /* The JSON-RPC request is invalid */
+#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */
+#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */
+#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */
+
+#endif // MJSON_ENABLE_RPC
+#ifdef __cplusplus
+}
+#endif
+#endif // MJSON_H
diff --git a/third_party/mcpc/src/prmpt.c b/third_party/mcpc/src/prmpt.c
new file mode 100644
index 0000000..324e5dd
--- /dev/null
+++ b/third_party/mcpc/src/prmpt.c
@@ -0,0 +1,378 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "prmpt.h"
+
+#include "ext_string.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include "errcode.h"
+#include "alloc.h"
+#include "anydata.h"
+
+/* = = = = = = = = = = = = = = = prmptarg = = = = = = = = = = = = = = = */
+
+MCPC_API mcpc_prmptarg_t *
+mcpc_prmptarg_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len)
+{
+ mcpc_anydata_t *hp = mcpc_anydata_new_meta1_meta2 (name, name_len, desc, desc_len);
+
+ hp->flags |= ANYDATA_FL_REQUIRED;
+ hp->flags |= ANYDATA_FL_PRMPTARG;
+
+ hp->data_typ = MCPC_U8STR;
+
+ return hp;
+}
+
+MCPC_API mcpc_prmptarg_t *
+mcpc_prmptarg_new2 (const char8_t *nament, const char8_t *descnt)
+{
+ return mcpc_prmptarg_new (nament, u8strlen (nament), descnt, u8strlen (descnt));
+}
+
+MCPC_API mcpc_prmptarg_t *
+mcpc_prmptarg_new3 (const char8_t *nament)
+{
+ return mcpc_prmptarg_new (nament, u8strlen (nament), nullptr, 0);
+}
+
+void
+mcpc_prmptarg_free (mcpc_prmptarg_t *prmptarg)
+{
+ mcpc_anydata_free ((mcpc_anydata_t *) prmptarg);
+}
+
+bool
+mcpc_prmptarg_is_required (const mcpc_prmptarg_t *prmptarg)
+{
+ return prmptarg->flags & ANYDATA_FL_REQUIRED;
+}
+
+mcpc_errcode_t
+mcpc_prmptarg_getref_name (const mcpc_prmptarg_t *prmptarg, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta1 (prmptarg, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_prmptarg_getref_desc (const mcpc_prmptarg_t *prmptarg, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta2 (prmptarg, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_prmptarg_get_data_type (const mcpc_prmptarg_t *prmptarg, mcpc_anytype_t *ret)
+{
+ bug_if_nullptr (prmptarg);
+ bug_if_nullptr (ret);
+ *ret = ((const mcpc_anydata_t *) prmptarg)->data_typ;
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_prmptarg_set_required (mcpc_prmptarg_t *prmptarg, bool fl)
+{
+ bug_if_nullptr (prmptarg);
+
+ if (fl)
+ {
+ prmptarg->flags |= ANYDATA_FL_REQUIRED;
+ }
+ else
+ {
+ prmptarg->flags &= (~ANYDATA_FL_REQUIRED);
+ }
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_prmptarg_set_nament (mcpc_prmptarg_t *prmptarg, const char8_t *name)
+{
+ return mcpc_anydata_set_meta1nt (prmptarg, name);
+}
+
+mcpc_errcode_t
+mcpc_prmptarg_set_u8str (mcpc_prmptarg_t *prmptarg, const char8_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_u8str (prmptarg, arr, arr_len);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_prmptarg_get_u8str (const mcpc_prmptarg_t *prmptarg, char8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_u8str (prmptarg, ret, ret_cap, ret_len);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_prmptarg_getref_u8str (const mcpc_prmptarg_t *prmptarg, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_u8str (prmptarg, ret, ret_len);
+}
+
+/* = = = = = = = = = = = = = = = = = prmpt = = = = = = = = = = = = = = = = = */
+
+MCPC_API mcpc_prmpt_t *
+mcpc_prmpt_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len)
+{
+ mcpc_anydata_t *hp = mcpc_anydata_new_meta1_meta2 (name, name_len, desc, desc_len);
+
+ hp->flags |= ANYDATA_FL_PRMPT;
+
+ return hp;
+}
+
+MCPC_API mcpc_prmpt_t *
+mcpc_prmpt_new2 (const char8_t *nament, const char8_t *descnt)
+{
+ return mcpc_prmpt_new (nament, strlen ((const char *) nament), descnt, strlen ((const char *) descnt));
+}
+
+MCPC_API mcpc_prmpt_t *
+mcpc_prmpt_new3 (const char8_t *nament)
+{
+ return mcpc_prmpt_new (nament, u8strlen (nament), nullptr, 0);
+}
+
+void
+mcpc_prmpt_free (mcpc_prmpt_t *prmpt)
+{
+ mcpc_anydata_free ((mcpc_anydata_t *) prmpt);
+}
+
+mcpc_errcode_t
+mcpc_prmpt_getref_name (const mcpc_prmpt_t *prmpt, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta1 (prmpt, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_prmpt_getref_desc (const mcpc_prmpt_t *prmpt, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta2 (prmpt, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_prmpt_get_callcb (const mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t *ret)
+{
+ return mcpc_anydata_get_func (prmpt, ret);
+}
+
+mcpc_errcode_t
+mcpc_prmpt_set_callcb (mcpc_prmpt_t *prmpt, mcpc_prmpt_callcb_t cb)
+{
+ mcpc_anydata_set_func (prmpt, cb);
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_prmpt_addfre_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg)
+{
+ mcpc_anydata_add_child (prmpt, prmptarg);
+ return MCPC_EC_0;
+
+}
+
+MCPC_API void
+mcpc_prmpt_addcpy_prmptarg (mcpc_prmpt_t *prmpt, mcpc_prmptarg_t *prmptarg)
+{
+ mcpc_anydata_addcpy_child (prmpt, prmptarg); // TODO return EC
+}
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_get_prmptarg (const mcpc_prmpt_t *prmpt, size_t idx)
+{
+ return mcpc_anydata_get_child (prmpt, idx);
+}
+
+MCPC_API size_t
+mcpc_prmpt_get_prmptarg_len (const mcpc_prmpt_t *prmpt)
+{
+ bug_if_nullptr (prmpt);
+ mcpc_anydata_t *prmpt_imp = (mcpc_anydata_t *) prmpt;
+ if (prmpt_imp->chd_pool == nullptr)
+ return 0;
+ return prmpt_imp->chd_pool->len;
+}
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_get_next_prmptarg (const mcpc_prmpt_t *prmpt)
+{
+ bug_if_nullptr (prmpt);
+
+ static size_t idx = 0;
+
+ const mcpc_anypool_t *pool = prmpt->chd_pool;
+ if (pool == nullptr)
+ return nullptr;
+
+ const size_t pool_len = pool->len;
+ if (pool_len == 0)
+ return nullptr;
+
+ if (idx == pool_len) // TODO MT-SAFE
+ {
+ idx = 0;
+ return nullptr;
+ }
+
+ const mcpc_anydata_t *ret = pool->head + idx;
+ idx += 1;
+
+ return ret;
+}
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_getbyname_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_name, size_t parg_name_len)
+{
+ const mcpc_anydata_t *chd = mcpc_anypool_getbymeta1 (prmpt->chd_pool, parg_name, parg_name_len);
+ return (mcpc_prmptarg_t *) chd;
+}
+
+MCPC_API const mcpc_prmptarg_t *
+mcpc_prmpt_getbynament_prmptarg (const mcpc_prmpt_t *prmpt, const char8_t *parg_nament)
+{
+ const mcpc_anydata_t *chd = mcpc_anypool_getbymeta1nt (prmpt->chd_pool, parg_nament);
+ return (mcpc_prmptarg_t *) chd;
+}
+
+/* = = = = = = = = = = = = = = = prmptpool = = = = = = = = = = = = = = = */
+
+mcpc_prmptpool_t *
+mcpc_prmptpool_new ()
+{
+ return mcpc_anypool_new ();
+}
+
+void
+mcpc_prmptpool_free (mcpc_prmptpool_t *prmptpool)
+{
+ mcpc_anypool_free (prmptpool);
+}
+
+void
+mcpc_prmptpool_addcpy (mcpc_prmptpool_t *prmptpool, const mcpc_prmpt_t *prmpt)
+{
+ mcpc_anypool_addcpy (prmptpool, prmpt);
+}
+
+void
+mcpc_prmptpool_addfre (mcpc_prmptpool_t *prmptpool, mcpc_prmpt_t *prmpt)
+{
+ mcpc_anypool_addfre (prmptpool, prmpt);
+}
+
+size_t
+mcpc_prmptpool_get_len (const mcpc_prmptpool_t *prmptpool)
+{
+ bug_if_nullptr (prmptpool);
+ size_t ret = 0;
+ ret = ((const mcpc_anypool_t *) prmptpool)->len;
+ return ret;
+}
+
+const mcpc_prmpt_t *
+mcpc_prmptpool_get (const mcpc_prmptpool_t *prmptpool, size_t idx)
+{
+ return mcpc_anypool_get (prmptpool, idx);
+}
+
+const mcpc_prmpt_t *
+mcpc_prmptpool_getbyname (const mcpc_prmptpool_t *prmptpool, const char8_t *name, size_t name_len)
+{
+ return mcpc_anypool_getbymeta1 (prmptpool, name, name_len);
+}
+
+/* -= = = = = = = = = = = = = = prmptarghint = = = = = = = = = = = = = = = */
+
+mcpc_prmptarghint_t *
+mcpc_prmptarghint_new ()
+{
+ mcpc_anydata_t *anydata = mcpc_anydata_new_typ (MCPC_U8STR);
+ anydata->flags |= ANYDATA_FL_PRMPTARGHINT;
+ return anydata;
+}
+
+mcpc_prmptarghint_t *
+mcpc_prmptarghint_new_vprintf8 (const char8_t *fmt, va_list *vargs)
+{
+ char8_t *sbuf = mcpc_alloc (1024);
+ int printed = vsprintf ((char *) sbuf, (const char *) fmt, *vargs);
+
+ mcpc_prmptarghint_t *prmptarghint = mcpc_prmptarghint_new ();
+ bug_if_nz (mcpc_anydata_set_u8str (prmptarghint, sbuf, (size_t) printed));
+
+ free (sbuf);
+ return prmptarghint;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_prmptarg_add_hint_printf8 (mcpc_prmptarg_t *prmptarg, const char8_t *fmt, ...)
+{
+ va_list vargs;
+ va_start (vargs, fmt);
+
+ mcpc_prmptarghint_t *hint = mcpc_prmptarghint_new_vprintf8 (fmt, &vargs);
+
+ if (prmptarg->chd_pool == nullptr)
+ prmptarg->chd_pool = mcpc_anypool_new ();
+
+ mcpc_anypool_addfre (prmptarg->chd_pool, hint);
+
+ va_end (vargs);
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_prmptarghint_getref_u8str (const mcpc_prmptarg_t *prmptarghint, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_u8str (prmptarghint, ret, ret_len);
+}
+
+const mcpc_prmptarghint_t *
+mcpc_prmptarg_get_next_prmptarghint (const mcpc_prmptarg_t *prmptarg)
+{
+ bug_if_nullptr (prmptarg);
+
+ static size_t idx = 0;
+
+ const mcpc_anypool_t *pool = prmptarg->chd_pool;
+ if (pool == nullptr)
+ return nullptr;
+
+ const size_t pool_len = pool->len;
+ if (pool_len == 0)
+ return nullptr;
+
+ if (idx == pool_len) // TODO MT-SAFE
+ {
+ idx = 0;
+ return nullptr;
+ }
+
+ const mcpc_anydata_t *ret = pool->head + idx;
+ idx += 1;
+
+ return ret;
+}
diff --git a/third_party/mcpc/src/prmpt.h b/third_party/mcpc/src/prmpt.h
new file mode 100644
index 0000000..2199864
--- /dev/null
+++ b/third_party/mcpc/src/prmpt.h
@@ -0,0 +1,80 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_prmpt
+#define h_prmpt
+
+#include <mcpc/prmpt.h>
+
+typedef mcpc_anypool_t mcpc_prmptpool_t;
+typedef mcpc_anypool_t mcpc_prmptargpool_t;
+
+// ---------------------------- prmptarg ----------------------------
+
+void mcpc_prmptarg_free (mcpc_prmptarg_t * prmptarg);
+
+bool mcpc_prmptarg_is_required (const mcpc_prmptarg_t * prmptarg);
+
+mcpc_errcode_t mcpc_prmptarg_getref_name (const mcpc_prmptarg_t * prmptarg, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_prmptarg_getref_desc (const mcpc_prmptarg_t * prmptarg, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_prmptarg_get_data_type (const mcpc_prmptarg_t * prmptarg, mcpc_anytype_t * ret);
+
+mcpc_errcode_t mcpc_prmptarg_set_required (mcpc_prmptarg_t * prmptarg, bool fl);
+
+mcpc_errcode_t mcpc_prmptarg_set_nament (mcpc_prmptarg_t * prmptarg, const char8_t * name);
+
+mcpc_errcode_t mcpc_prmptarg_set_u8str (mcpc_prmptarg_t * prmptarg, const char8_t * arr, size_t arr_len);
+
+// ------------------------------ prmpt ------------------------------
+
+mcpc_errcode_t mcpc_prmpt_getref_name (const mcpc_prmpt_t * prmpt, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_prmpt_getref_desc (const mcpc_prmpt_t * prmpt, const char8_t ** ret, size_t *ret_len);
+
+void mcpc_prmpt_free (mcpc_prmpt_t * prmpt);
+
+// ---------------------------- prmptpool ----------------------------
+
+mcpc_prmptpool_t *mcpc_prmptpool_new ();
+
+void mcpc_prmptpool_free (mcpc_prmptpool_t * prmptpool);
+
+void mcpc_prmptpool_addcpy (mcpc_prmptpool_t * prmptpool, const mcpc_prmpt_t * prmpt);
+
+void mcpc_prmptpool_addfre (mcpc_prmptpool_t * prmptpool, mcpc_prmpt_t * prmpt);
+
+size_t mcpc_prmptpool_get_len (const mcpc_prmptpool_t * prmptpool);
+
+const mcpc_prmpt_t *mcpc_prmptpool_get (const mcpc_prmptpool_t * prmptpool, size_t idx);
+
+const mcpc_prmpt_t *mcpc_prmptpool_getbyname (const mcpc_prmptpool_t * prmptpool, const char8_t * name,
+ size_t name_len);
+
+// -------------------------- prmptarghint --------------------------
+
+const mcpc_prmptarghint_t *mcpc_prmptarg_get_next_prmptarghint (const mcpc_prmptarg_t * prmptarg);
+
+mcpc_errcode_t
+mcpc_prmptarghint_getref_u8str (const mcpc_prmptarg_t * prmptarghint, const char8_t ** ret, size_t *ret_len);
+
+#endif
diff --git a/third_party/mcpc/src/retbuf.c b/third_party/mcpc/src/retbuf.c
new file mode 100644
index 0000000..0429f15
--- /dev/null
+++ b/third_party/mcpc/src/retbuf.c
@@ -0,0 +1,119 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "retbuf.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mjson.h"
+
+retbuf_t
+retbuf_new (size_t cap, size_t *len)
+{
+ retbuf_t x = {
+ .cap = cap,.buf0 = malloc (cap),.len = len
+ };
+ return x;
+}
+
+void
+retbuf_free (retbuf_t rtbf)
+{
+ free (rtbf.buf0);
+}
+
+uint8_t
+rtbuf_printf_adv (retbuf_t rtbf, size_t advl, const char8_t *u8str, ...)
+{
+ va_list args;
+ int printed;
+ size_t remaining;
+ (void) advl;
+ if (rtbf.buf0 == nullptr || rtbf.len == nullptr || *rtbf.len >= rtbf.cap)
+ return 1;
+ va_start (args, u8str);
+ remaining = rtbf.cap - *rtbf.len;
+ printed = vsnprintf (rtbf.buf0 + *rtbf.len, remaining, (const char *) u8str, args);
+ va_end (args);
+ if (printed < 0 || (size_t) printed >= remaining)
+ {
+ *rtbf.len = rtbf.cap;
+ return 1;
+ }
+ *rtbf.len += (size_t) printed;
+ return 0;
+}
+
+uint8_t
+rtbuf_printf (retbuf_t rtbf, const char8_t *u8str, ...)
+{
+ va_list args;
+ int printed;
+ size_t remaining;
+ if (rtbf.buf0 == nullptr || rtbf.len == nullptr || *rtbf.len >= rtbf.cap)
+ return 1;
+ va_start (args, u8str);
+ remaining = rtbf.cap - *rtbf.len;
+ printed = vsnprintf (rtbf.buf0 + *rtbf.len, remaining, (const char *) u8str, args);
+ va_end (args);
+ if (printed < 0 || (size_t) printed >= remaining)
+ {
+ *rtbf.len = rtbf.cap;
+ return 1;
+ }
+ *rtbf.len += (size_t) printed;
+ return 0;
+}
+
+uint8_t
+rtbuf_printf_mjfmt (retbuf_t rtbf, const char8_t *fmt8, ...)
+{
+ va_list args;
+ va_start (args, fmt8);
+
+ const char *fmt = (const char *) fmt8;
+ struct mjson_fixedbuf fb = { rtbf.buf0, (int)rtbf.cap, (int)*rtbf.len };
+ int advl = mjson_vprintf (&mjson_print_fixed_buf, &fb, fmt, &args);
+
+ if (advl < 0 || (size_t) advl > rtbf.cap - *rtbf.len)
+ *rtbf.len = rtbf.cap;
+ else
+ *rtbf.len += (size_t) advl;
+
+ va_end (args);
+ return 0;
+}
+
+// TODO change _printf_ use this one
+u8_t
+rtbuf_vprintf_mjfmt (retbuf_t rtbf, const char8_t *fmt8, va_list *vargs)
+{
+ const char *fmt = (const char *) fmt8;
+ struct mjson_fixedbuf fb = { rtbf.buf0, (int)rtbf.cap, (int)*rtbf.len };
+ int advl = mjson_vprintf (&mjson_print_fixed_buf, &fb, fmt, vargs); // TODO: __va_list_tag (*)[1]’
+ if (advl < 0 || (size_t) advl > rtbf.cap - *rtbf.len)
+ *rtbf.len = rtbf.cap;
+ else
+ *rtbf.len += (size_t) advl;
+ return 0;
+}
diff --git a/third_party/mcpc/src/retbuf.h b/third_party/mcpc/src/retbuf.h
new file mode 100644
index 0000000..eadcce4
--- /dev/null
+++ b/third_party/mcpc/src/retbuf.h
@@ -0,0 +1,50 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_retbuf
+#define h_retbuf
+
+#include <stdarg.h>
+
+#include <mcpc/_c23_uchar.h>
+
+#include "ext_stdint.h"
+
+
+typedef struct retbuf
+{
+ const size_t cap;
+ char *const buf0;
+ size_t *len;
+} retbuf_t;
+
+retbuf_t retbuf_new (size_t cap, size_t *len);
+
+void retbuf_free (retbuf_t rtbf);
+
+u8_t rtbuf_printf_adv (retbuf_t rtbf, size_t advl, const char8_t * u8str, ...);
+
+u8_t rtbuf_printf (retbuf_t rtbf, const char8_t * u8str, ...);
+
+u8_t rtbuf_printf_mjfmt (retbuf_t rtbf, const char8_t * u8str, ...);
+
+
+#endif
diff --git a/third_party/mcpc/src/rsc.c b/third_party/mcpc/src/rsc.c
new file mode 100644
index 0000000..be5627b
--- /dev/null
+++ b/third_party/mcpc/src/rsc.c
@@ -0,0 +1,177 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "rsc.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "ext_string.h"
+
+#include "errcode.h"
+#include "alloc.h"
+#include "anydata.h"
+
+MCPC_API mcpc_rsc_t *
+mcpc_rsc_new (const char8_t *uri, const size_t uri_len, const char8_t *name, const size_t name_len)
+{
+ mcpc_anydata_t *anydata = mcpc_anydata_new_typ_meta1 (MCPC_U8STR, name, name_len);
+
+ anydata->flags |= ANYDATA_FL_RSC;
+ anydata->data_mime = MCPC_MIME_TEXT_PLAIN;
+
+ if (MCPC_EC_0 != mcpc_anydata_set_u8str (anydata, uri, uri_len))
+ goto badret;
+
+ return (mcpc_rsc_t *) anydata;
+
+badret:
+ mcpc_anydata_free (anydata);
+ return nullptr;
+}
+
+MCPC_API mcpc_rsc_t *
+mcpc_rsc_new2 (const char8_t *uri_nt, const char8_t *nament)
+{
+ return mcpc_rsc_new (uri_nt, u8strlen (uri_nt), nament, u8strlen (nament));
+}
+
+MCPC_API mcpc_rsc_t *
+mcpc_rsc_new3 (mcpc_mime_t mime, const char8_t *uri_nt, const char8_t *nament)
+{
+ mcpc_rsc_t *rsc = mcpc_rsc_new2 (uri_nt, nament);
+ rsc->data_mime = mime;
+ return rsc;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_get_uri (const mcpc_rsc_t *rsc, char8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_u8str (rsc, ret, ret_cap, ret_len);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_getref_uri (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_u8str (rsc, ret, ret_len);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_getref_name (const mcpc_rsc_t *rsc, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta1 (rsc, ret, ret_len);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_rsc_get_mime (const mcpc_rsc_t *rsc, mcpc_mime_t *ret)
+{
+ bug_if_nullptr (rsc);
+ bug_if_nullptr (ret);
+ *ret = rsc->data_mime;
+ return MCPC_EC_0;
+}
+
+MCPC_API void
+mcpc_rsc_set_mime (mcpc_rsc_t *rsc, mcpc_mime_t mime)
+{
+ bug_if_nullptr (rsc);
+ rsc->data_mime = mime;
+}
+
+MCPC_API void
+mcpc_rsc_free (mcpc_rsc_t *rsc)
+{
+ mcpc_anydata_free (rsc);
+}
+
+/* = = = = = = = = = = = = = = = = rscpool = = = = = = = = = = = = = = = = */
+
+mcpc_rscpool_t *
+mcpc_rscpool_new ()
+{
+ return mcpc_anypool_new ();
+}
+
+void
+mcpc_rscpool_free (mcpc_rscpool_t *rscpool)
+{
+ mcpc_anypool_free (rscpool);
+}
+
+void
+mcpc_rscpool_addcpy (mcpc_rscpool_t *rscpool, const mcpc_rsc_t *rsc)
+{
+ mcpc_anypool_addcpy (rscpool, rsc);
+}
+
+void
+mcpc_rscpool_addfre (mcpc_rscpool_t *rscpool, mcpc_rsc_t *rsc)
+{
+ mcpc_anypool_addfre (rscpool, rsc);
+}
+
+size_t
+mcpc_rscpool_get_len (const mcpc_rscpool_t *rscpool)
+{
+ bug_if_nullptr (rscpool);
+ size_t ret = 0;
+ ret = ((const mcpc_anypool_t *) rscpool)->len;
+ return ret;
+}
+
+const mcpc_rsc_t *
+mcpc_rscpool_get (const mcpc_rscpool_t *rscpool, size_t idx)
+{
+ return mcpc_anypool_get (rscpool, idx);
+}
+
+const mcpc_rsc_t *
+mcpc_rscpool_getbyname (const mcpc_rscpool_t *rscpool, const char8_t *name, size_t name_len)
+{
+ return mcpc_anypool_getbymeta1 (rscpool, name, name_len);
+}
+
+const mcpc_rsc_t *
+mcpc_rscpool_get_next (const mcpc_rscpool_t *rscpool)
+{
+ bug_if_nullptr (rscpool);
+
+ static size_t idx = 0;
+
+ const mcpc_anypool_t *pool = rscpool;
+ if (pool == nullptr)
+ return nullptr;
+
+ const size_t pool_len = pool->len;
+ if (pool_len == 0)
+ return nullptr;
+
+ if (idx == pool_len) // TODO MT-SAFE
+ {
+ idx = 0;
+ return nullptr;
+ }
+
+ const mcpc_anydata_t *ret = pool->head + idx;
+ idx += 1;
+
+ return ret;
+}
diff --git a/third_party/mcpc/src/rsc.h b/third_party/mcpc/src/rsc.h
new file mode 100644
index 0000000..d9f1477
--- /dev/null
+++ b/third_party/mcpc/src/rsc.h
@@ -0,0 +1,51 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_rsc
+#define h_rsc
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include <mcpc/_c23_keywords.h>
+#include <mcpc/_c23_uchar.h>
+
+#include <mcpc/rsc.h>
+
+typedef mcpc_anypool_t mcpc_rscpool_t;
+
+// ---------------------------- rscpool ----------------------------
+
+mcpc_rscpool_t *mcpc_rscpool_new ();
+
+void mcpc_rscpool_free (mcpc_rscpool_t * rscpool);
+
+void mcpc_rscpool_add_cp (mcpc_rscpool_t * rscpool, const mcpc_rsc_t * tmp_rsc);
+
+void mcpc_rscpool_addfre (mcpc_rscpool_t * rscpool, mcpc_rsc_t * tmp_rsc);
+
+const mcpc_rsc_t *mcpc_rscpool_get_const (const mcpc_rscpool_t * rscpool, size_t idx);
+
+const mcpc_rsc_t *mcpc_rscpool_get_next (const mcpc_rscpool_t * rscpool);
+
+const mcpc_rsc_t *mcpc_rscpool_getbyname (const mcpc_rscpool_t * rscpool, const char8_t * name, size_t name_len);
+
+#endif
diff --git a/third_party/mcpc/src/serlz.c b/third_party/mcpc/src/serlz.c
new file mode 100644
index 0000000..0721233
--- /dev/null
+++ b/third_party/mcpc/src/serlz.c
@@ -0,0 +1,619 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#define __TU__ "serlz"
+
+#include "serlz.h"
+
+#include "ext_string.h"
+#include "log.h"
+
+
+/* = = = = = = = = = = = = = = = = = tool = = = = = = = = = = = = = = = = = */
+
+u8_t
+serlz_tool_inputschema_properties_prop (retbuf_t rtbf, const mcpc_toolprop_t *tprop)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ const char8_t *name, *desc = nullptr;
+ size_t name_len, desc_len = 0;
+
+ bug_if_nz (mcpc_toolprop_getref_name (tprop, &name, &name_len));
+ bug_if_nz (mcpc_toolprop_getref_desc (tprop, &desc, &desc_len));
+
+ mcpc_anytype_t typ = MCPC_NONE;
+ bug_if_nz (mcpc_toolprop_get_data_type (tprop, &typ));
+
+ if (false)
+ ;
+ else if (mcpc_anytype_is_intlike (typ))
+ {
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%Q"), "type", "integer");
+ }
+ else if (typ == MCPC_U8STR)
+ {
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%Q"), "type", "string");
+ }
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "title", name_len, name);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "description", desc_len, desc);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+u8_t
+serlz_tool_inputschema_properties (retbuf_t rtbf, const mcpc_toolproppool_t *tppool)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ if (tppool != nullptr)
+ {
+ size_t len_tppool = mcpc_toolproppool_get_len (tppool);
+ if (len_tppool > 0)
+ {
+ for (size_t i = 0; i < len_tppool; i++)
+ {
+ const mcpc_toolprop_t *tprop = mcpc_toolproppool_get (tppool, i);
+ size_t name_len = 0;
+ const char8_t *name = nullptr;
+ bug_if (mcpc_toolprop_getref_name (tprop, &name, &name_len));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%.*Q:"), name_len, name);
+ serlz_tool_inputschema_properties_prop (rtbf, tprop);
+ if (i != len_tppool - 1)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ }
+ }
+ }
+
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+u8_t
+serlz_tool_inputschema (retbuf_t rtbf, const mcpc_tool_t *tool)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%Q"), "type", "object");
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "properties");
+ serlz_tool_inputschema_properties (rtbf, mcpc_tool_get_toolproppool (tool));
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:["), "required");
+ bool first = true;
+ size_t property_count = mcpc_tool_get_toolprop_len (tool);
+ for (size_t i = 0; i < property_count; ++i)
+ {
+ const mcpc_toolprop_t *property = mcpc_tool_get_toolprop (tool, i);
+ const char8_t *name = nullptr;
+ size_t name_len = 0;
+ if (!mcpc_toolprop_is_required (property))
+ continue;
+ bug_if_nz (mcpc_toolprop_getref_name (property, &name, &name_len));
+ if (!first)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%.*Q"), name_len, name);
+ first = false;
+ }
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("]"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_tool (retbuf_t rtbf, const mcpc_tool_t *tool)
+{
+ const char8_t *name, *desc = nullptr;
+ size_t name_len, desc_len = 0;
+ bug_if_nz (mcpc_toolprop_getref_name (tool, &name, &name_len));
+ bug_if_nz (mcpc_toolprop_getref_desc (tool, &desc, &desc_len));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "name", name_len, name);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "description", desc_len, desc);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "inputSchema");
+ serlz_tool_inputschema (rtbf, tool);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+u8_t
+serlz_toolpool (retbuf_t rtbf, const mcpc_toolpool_t *toolpool)
+{
+ size_t len_tpool = mcpc_toolpool_get_len (toolpool);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "tools");
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("["));
+
+ // TODO TODO TODO use iterator
+ if (toolpool != nullptr && len_tpool > 0)
+ {
+ for (size_t i = 0; i < len_tpool; i++)
+ {
+ const mcpc_tool_t *cur_tool = mcpc_toolpool_get (toolpool, i);
+ serlz_tool (rtbf, cur_tool);
+ if (i != len_tpool - 1)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ }
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("]"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+/* = = = = = = = = = = = = = = = = = prmpt = = = = = = = = = = = = = = = = = */
+
+u8_t
+serlz_prmptarg (retbuf_t rtbf, const mcpc_prmptarg_t *prmptarg)
+{
+ const char8_t *name, *desc = nullptr;
+ size_t name_len, desc_len = 0;
+ bool required = false;
+
+ bug_if_nz (mcpc_prmptarg_getref_name (prmptarg, &name, &name_len));
+ bug_if_nz (mcpc_prmptarg_getref_desc (prmptarg, &desc, &desc_len));
+ required = mcpc_prmptarg_is_required (prmptarg);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "name", name_len, name);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "description", desc_len, desc);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%B"), "required", required);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_prmpt (retbuf_t rtbf, const mcpc_prmpt_t *prmpt)
+{
+ const char8_t *name, *desc = nullptr;
+ size_t name_len, desc_len = 0;
+ bug_if_nz (mcpc_prmptarg_getref_name (prmpt, &name, &name_len));
+ bug_if_nz (mcpc_prmptarg_getref_desc (prmpt, &desc, &desc_len));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "name", name_len, name);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "description", desc_len, desc);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "arguments");
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("["));
+
+ const mcpc_prmptarg_t *cur_prmptarg = nullptr;
+ bool first = true;
+ while ((cur_prmptarg = mcpc_prmpt_get_next_prmptarg (prmpt)))
+ {
+ if (!first)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ serlz_prmptarg (rtbf, cur_prmptarg);
+ first = false;
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("]"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+u8_t
+serlz_prmptpool (retbuf_t rtbf, const mcpc_prmptpool_t *prmptpool)
+{
+ size_t len_tpool = mcpc_prmptpool_get_len (prmptpool);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "prompts");
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("["));
+
+ if (prmptpool != nullptr && len_tpool > 0)
+ {
+ for (size_t i = 0; i < len_tpool; i++)
+ {
+ const mcpc_prmpt_t *cur_prmpt = mcpc_prmptpool_get (prmptpool, i);
+ serlz_prmpt (rtbf, cur_prmpt);
+ if (i != len_tpool - 1)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ }
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("]"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+/* = = = = = = = = = = = = = = = = = = rsc = = = = = = = = = = = = = = = = = = */
+
+u8_t
+serlz_rsc (retbuf_t rtbf, const mcpc_rsc_t *rsc)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ const char8_t *ref_uri = nullptr;
+ size_t ref_uri_len = 0;
+ const char8_t *ref_name = nullptr;
+ size_t ref_name_len = 0;
+ mcpc_mime_t mime = MCPC_MIME_NONE;
+
+ mcpc_rsc_getref_uri (rsc, &ref_uri, &ref_uri_len);
+ mcpc_rsc_getref_name (rsc, &ref_name, &ref_name_len);
+ mcpc_rsc_get_mime (rsc, &mime);
+
+ bool serlz_uri = ref_uri_len > 0 && ref_uri != nullptr;
+ bool serlz_name = ref_name_len > 0 && ref_name != nullptr;
+ bool serlz_mime = mime != MCPC_MIME_NONE;
+
+ // uri is ID, hence first
+ if (serlz_uri)
+ {
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "uri", ref_uri_len, (const char *) ref_uri);
+ }
+
+ if (serlz_name)
+ {
+ if (serlz_uri)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "name", ref_name_len, ref_name);
+ }
+
+ if (serlz_mime)
+ {
+ if (serlz_name || serlz_uri)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "mimeType");
+ if (false);
+ else if (mime == MCPC_MIME_TEXT_PLAIN)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("text/plain"));
+ else if (mime == MCPC_MIME_IMAGE_PNG)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("image/png"));
+ else if (mime == MCPC_MIME_IMAGE_JPEG)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("image/jpeg"));
+ else if (mime == MCPC_MIME_IMAGE_GIF)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("image/gif"));
+ else if (mime == MCPC_MIME_AUDIO_MPEG)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("audio/mpeg"));
+ else if (mime == MCPC_MIME_AUDIO_WAV)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("audio/wav"));
+ else if (mime == MCPC_MIME_VIDEO_MPEG)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("video/mpeg"));
+ else if (mime == MCPC_MIME_VIDEO_AVI)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("video/avi"));
+ else if (mime == MCPC_MIME_APPLI_OCTS)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("application/octet-stream"));
+ else if (mime == MCPC_MIME_APPLI_JSON)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("application/json"));
+ else if (mime == MCPC_MIME_APPLI_PDF)
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), u8c1 ("application/pdf"));
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+
+ return 0;
+}
+
+u8_t
+serlz_rscpool (retbuf_t rtbf, const mcpc_rscpool_t *rscpool)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "resources");
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("["));
+
+
+ const mcpc_rsc_t *cur_rsc = nullptr;
+ bool first = true;
+ while ((cur_rsc = mcpc_rscpool_get_next (rscpool)))
+ {
+ if (!first)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ serlz_rsc (rtbf, cur_rsc);
+ first = false;
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("]"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+/* = = = = = = = = = = = = = = = = = = = - = = = = = = = = = = = = = = = = = = = */
+
+uint8_t
+serlz_serverInfo (retbuf_t rtbf, const mcpc_server_t *sv)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "name", sv->name_len, sv->name);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%.*Q"), "version", sv->ver_len, sv->ver);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_capasprompts (retbuf_t rtbf, const mcpc_server_t *sv)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "listChanged");
+ bool supp_lschg = mcpc_server_supp_prompts_lschg (sv);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%B"), supp_lschg);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_capastools (retbuf_t rtbf, const mcpc_server_t *sv)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "listChanged");
+ bool supp_lschg = mcpc_server_supp_tools_lschg (sv);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%B"), supp_lschg);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_capasresources (retbuf_t rtbf, const mcpc_server_t *sv)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "subscribe");
+ bool supp_subs = mcpc_server_supp_rscs_subs (sv);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%B"), supp_subs);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "listChanged");
+ bool supp_lschg = mcpc_server_supp_rscs_lschg (sv);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%B"), supp_lschg);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_capas (retbuf_t rtbf, const mcpc_server_t *sv)
+{
+ bool need_comma = false;
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ if (mcpc_server_supp_prompts (sv))
+ {
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "prompts");
+ serlz_capasprompts (rtbf, sv);
+ need_comma = true;
+ }
+
+ if (mcpc_server_supp_tools (sv))
+ {
+ if (need_comma)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "tools");
+ serlz_capastools (rtbf, sv);
+ need_comma = true;
+ }
+
+ if (mcpc_server_supp_rscs (sv))
+ {
+ if (need_comma)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "resources");
+ serlz_capasresources (rtbf, sv);
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_initres (retbuf_t rtbf, const mcpc_server_t *sv)
+{
+ rtbuf_printf (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%Q"), "protocolVersion", "2024-11-05");
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "serverInfo");
+ serlz_serverInfo (rtbf, sv);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "capabilities");
+ serlz_capas (rtbf, sv);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+/* = = = = = = = = = = = = = = = = = ucbr = = = = = = = = = = = = = = = = = */
+
+uint8_t
+serlz_ucbrchd_tool_callres (retbuf_t rtbf, const mcpc_ucbrchd_t *ucbrchd)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%Q"), "type", "text");
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "text");
+
+ mcpc_anytype_t data_type = MCPC_NONE;
+ bug_if_nz (mcpc_ucbrchd_get_data_type (ucbrchd, &data_type));
+
+ if (false)
+ ;
+ else if (data_type == MCPC_U8STR)
+ {
+ const char8_t *s = nullptr;
+ size_t s_len = 0;
+ mcpc_ucbrchd_getref_u8str (ucbrchd, &s, &s_len);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%.*Q"), s_len, (const char *) s);
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_ucbrchd_prmpt_callres (retbuf_t rtbf, const mcpc_ucbrchd_t *ucbrchd)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "role");
+ if (mcpc_ucbrchd_is_assist_prmpt (ucbrchd))
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), "assistant");
+ else
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q"), "user");
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "content");
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%Q"), "type", "text");
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "text");
+
+ mcpc_anytype_t data_type = MCPC_NONE;
+ bug_if_nz (mcpc_ucbrchd_get_data_type (ucbrchd, &data_type));
+
+ if (false)
+ ;
+ else if (data_type == MCPC_U8STR)
+ {
+ const char8_t *s = nullptr;
+ size_t s_len = 0;
+ mcpc_ucbrchd_getref_u8str (ucbrchd, &s, &s_len);
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%.*Q"), s_len, (const char *) s);
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+uint8_t
+serlz_ucbrchd (retbuf_t rtbf, const mcpc_ucbrchd_t *ucbrchd)
+{
+ if (false);
+ else if (mcpc_ucbrchd_is_tool_callres (ucbrchd))
+ {
+ return serlz_ucbrchd_tool_callres (rtbf, ucbrchd);
+ }
+ else if (mcpc_ucbrchd_is_prmpt_callres (ucbrchd))
+ {
+ return serlz_ucbrchd_prmpt_callres (rtbf, ucbrchd);
+ }
+ return 0;
+}
+
+u8_t
+serlz_ucbr (retbuf_t rtbf, const mcpc_ucbr_t *ucbr)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("["));
+
+ size_t length = mcpc_ucbr_get_len (ucbr);
+ for (size_t i = 0; i < length; ++i)
+ {
+ const mcpc_ucbrchd_t *cur_ucbrchd = mcpc_ucbr_get (ucbr, i);
+ if (i != 0)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ serlz_ucbrchd (rtbf, cur_ucbrchd);
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("]"));
+ return 0;
+}
+
+u8_t
+serlz_tool_callres (retbuf_t rtbf, const mcpc_ucbr_t *ucbr)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ if (mcpc_ucbr_is_err (ucbr))
+ {
+ // https://modelcontextprotocol.io/docs/concepts/tools#error-handling-2
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%B"), "isError", true);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "content");
+ serlz_ucbr (rtbf, ucbr);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+u8_t
+serlz_prmpt_callres (retbuf_t rtbf, const mcpc_ucbr_t *ucbr)
+{
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%Q"), "description", "Prompt List");
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "messages");
+ serlz_ucbr (rtbf, ucbr);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
+
+/* = = = = = = = = = = = = = = = = complt = = = = = = = = = = = = = = = = */
+
+u8_t
+serlz_complt (retbuf_t rtbf, const mcpc_complt_t *complt)
+{
+ size_t total = 0;
+
+ mcpc_complt_get_compltcandi_len (complt, &total);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "completion");
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("{"));
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%lu"), "total", total);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:%B"), "hasMore", false);
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%Q:"), "values");
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("["));
+
+ const mcpc_compltcand_t *cur_compltcand = nullptr;
+ bool first = true;
+ while ((cur_compltcand = mcpc_complt_get_next_compltcand (complt)))
+ {
+ if (!first)
+ rtbuf_printf_mjfmt (rtbf, u8c1 (","));
+ const char8_t *sbuf = nullptr;
+ size_t sbuf_len = 0;
+ bug_if_nz (mcpc_compltcand_getref_u8str (cur_compltcand, &sbuf, &sbuf_len));
+ bug_if_nullptr (sbuf);
+ bug_if_zero (sbuf_len);
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("%.*Q"), sbuf_len, sbuf);
+ first = false;
+ }
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("]"));
+
+
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ rtbuf_printf_mjfmt (rtbf, u8c1 ("}"));
+ return 0;
+}
diff --git a/third_party/mcpc/src/serlz.h b/third_party/mcpc/src/serlz.h
new file mode 100644
index 0000000..9ee6618
--- /dev/null
+++ b/third_party/mcpc/src/serlz.h
@@ -0,0 +1,62 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_serialize
+#define h_serialize
+
+#include "retbuf.h"
+#include "tool.h"
+#include "rsc.h"
+#include "server.h"
+#include "ucbr.h"
+#include "complt.h"
+
+#define L_QUO u8c1("\"")
+#define R_QUO u8c1("\"")
+#define L_QUO_D u8c1("(")
+#define R_QUO_D u8c1(")")
+
+u8_t serlz_tool (retbuf_t rtbf, const mcpc_tool_t * tool);
+
+u8_t serlz_toolpool (retbuf_t rtbf, const mcpc_toolpool_t * pool);
+
+u8_t serlz_prmptpool (retbuf_t rtbf, const mcpc_prmptpool_t * prmptpool);
+
+u8_t serlz_initres (retbuf_t rtbf, const mcpc_server_t * sv);
+
+u8_t serlz_rsc (retbuf_t rtbf, const mcpc_rsc_t * rsc);
+
+u8_t serlz_rscpool (retbuf_t rtbf, const mcpc_rscpool_t * rscpool);
+
+// deprecated
+u8_t serlz_ucbrchd (retbuf_t rtbf, const mcpc_ucbrchd_t * ucbrchd);
+
+// deprecated
+u8_t serlz_ucbr (retbuf_t rtbf, const mcpc_ucbr_t * ucbr);
+
+u8_t serlz_tool_callres (retbuf_t rtbf, const mcpc_ucbr_t * ucbr);
+
+u8_t serlz_prmpt_callres (retbuf_t rtbf, const mcpc_ucbr_t * ucbr);
+
+u8_t serlz_complt (retbuf_t rtbf, const mcpc_complt_t * complt);
+
+
+#endif
diff --git a/third_party/mcpc/src/server.c b/third_party/mcpc/src/server.c
new file mode 100644
index 0000000..07f8e8b
--- /dev/null
+++ b/third_party/mcpc/src/server.c
@@ -0,0 +1,1830 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#define __TU__ "server"
+
+#include "server.h"
+
+#include "ext_string.h"
+
+#include "alloc.h"
+#include "anydata.h"
+#include <mcpc/anydata.h>
+#include "errcode.h"
+#include "log.h"
+#include "retbuf.h"
+#include "rsc.h"
+#include "tool.h"
+#include "ucbr.h"
+#include "serlz.h"
+
+#include <limits.h>
+#include <math.h>
+
+#define BUFCAP_INIT_JSONREQ 1024
+#define CH_SPC 0x20
+#define CH_LF 0x0a
+#define CH_LBRC 0x7b
+#define CH_RBRC 0x7d
+#define CH_QUO 0x22
+#define CH_BSLASH 0x5c
+
+
+#if is_unix
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#elif is_win
+#include <winsock2.h>
+typedef i32_t socklen_t;
+#endif
+
+struct mjson_fixedbuf
+_new_mjson_fixedbuf ()
+{
+ struct mjson_fixedbuf fbuf = { nullptr, 0, 0 };
+ char *buf = mcpc_alloc (65536);
+ fbuf.ptr = buf;
+ fbuf.size = 65536;
+ fbuf.len = 0;
+ return fbuf;
+}
+
+bool
+ensure_buf_suffi (struct mjson_fixedbuf *fbuf, size_t ipt_len)
+{
+ char *grown;
+ if (fbuf == nullptr || ipt_len == 0 || ipt_len > INT_MAX)
+ return false;
+ if (ipt_len <= (size_t) fbuf->size)
+ {
+ return true;
+ }
+
+ grown = mcpc_realloc (fbuf->ptr, ipt_len);
+ if (grown == nullptr)
+ return false;
+ fbuf->ptr = grown;
+ fbuf->size = (int) ipt_len;
+ return true;
+}
+
+void
+_connpool_init (mcpc_connpool_t *ret)
+{
+ mcpc_connpool_t connpool = { 255, 0, nullptr };
+ memcpy (ret, &connpool, sizeof (mcpc_connpool_t));
+}
+
+bool
+_connpool_is_buggy (const mcpc_connpool_t *connpool)
+{
+ if (connpool == nullptr)
+ {
+ return true;
+ }
+ if (connpool->cap == 0)
+ {
+ return true;
+ }
+ if (connpool->len != 0)
+ {
+ u8_t nexist = 0;
+ mcpc_conn_t *cur_conn = connpool->head;
+ while (cur_conn != nullptr)
+ {
+ cur_conn = cur_conn->nex;
+ nexist += 1; // OR
+ // mcpc_assert(!ckd_add(&nexist, nexist, 1), MCPC_EC_BUG);
+ }
+ if (nexist != connpool->len)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+// extract a field's unquoted string
+mcpc_errcode_t
+_tulc_jstr_get_field_u8str (struct jsonrpc_request *r, const char8_t *jstr, size_t jstr_len, const char8_t *jpathnt,
+ char8_t **ret, size_t *ret_len, size_t ret_cap, bool not_empty)
+{
+ const char *jstr2 = (const char *) jstr;
+ int jstr_len2 = (int) jstr_len;
+ const char *jpathnt2 = (const char *) jpathnt;
+ const char8_t *ret_q = nullptr;
+ int32_t ret_q_len = 0;
+ int tmptok;
+
+ tmptok = mjson_find (jstr2, jstr_len2, jpathnt2, (const char **) &ret_q, (int *) &ret_q_len);
+ if (tmptok != MJSON_TOK_STRING || ret_q == nullptr || ret_q_len == 0)
+ goto field_invalid;
+
+ int getres = mjson_get_string ((const char *) ret_q, ret_q_len, "$", (char *) *ret, ret_cap);
+ if (getres == -1)
+ return MCPC_EC_BUFCAP;
+ if (not_empty && getres == 0)
+ goto field_invalid;
+ *ret_len = (size_t) getres;
+
+ return MCPC_EC_0;
+
+field_invalid:
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "field invalid", "%Q", jpathnt2);
+ return MCPC_EC_JSON_FIELD_INVALID;
+}
+
+// extract a field's unquoted string
+mcpc_errcode_t
+_tulc_jparams_get_field_u8str (struct jsonrpc_request *r,
+ const char8_t *jpathnt, char8_t **ret, size_t *ret_len, size_t ret_cap, bool not_empty)
+{
+ return _tulc_jstr_get_field_u8str (r, (const char8_t *) r->params, r->params_len, jpathnt, ret, ret_len,
+ ret_cap, not_empty);
+}
+
+// extract a field's value, that is, everything
+mcpc_errcode_t
+_tulc_jparams_getref_field_obj (struct jsonrpc_request *r, const char8_t *jpathnt, const char8_t **ret, size_t *ret_len)
+{
+ const char *jpathnt2 = (const char *) jpathnt;
+ int tmptok;
+
+ tmptok = mjson_find (r->params, r->params_len, jpathnt2, (const char **) ret, (int *) ret_len);
+ if (tmptok != MJSON_TOK_OBJECT || ret == nullptr || ret_len == 0)
+ goto field_invalid;
+
+ return MCPC_EC_0;
+
+field_invalid:
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "field invalid", "%Q", jpathnt2);
+ return MCPC_EC_JSON_FIELD_INVALID;
+}
+
+
+// extract a field's value, that is, everything
+mcpc_errcode_t
+_tulc_jparams_getref_field_u8str_q (struct jsonrpc_request *r, const char8_t *jpathnt, const char8_t **ret,
+ size_t *ret_len)
+{
+ const char *jpathnt2 = (const char *) jpathnt;
+ int tmptok;
+
+ tmptok = mjson_find (r->params, r->params_len, jpathnt2, (const char **) ret, (int *) ret_len);
+ if (tmptok != MJSON_TOK_STRING || ret == nullptr || ret_len == 0)
+ goto field_invalid;
+
+ return MCPC_EC_0;
+
+field_invalid:
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "field invalid", "%Q", jpathnt2);
+ return MCPC_EC_JSON_FIELD_INVALID;
+}
+
+
+// extract a field's value, that is, everything
+mcpc_errcode_t
+_tulc_jstr_get_field_number (struct jsonrpc_request *r, const char8_t *jstr, size_t jstr_len,
+ const char8_t *jpathnt, double *ret)
+{
+ const char *jpathnt2 = (const char *) jpathnt;
+ double numval = 0;
+
+ int ngot = mjson_get_number ((const char *) jstr, jstr_len, jpathnt2, &numval);
+
+ if (ngot != 1)
+ goto field_invalid;
+
+ *ret = numval;
+
+ return MCPC_EC_0;
+
+field_invalid:
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "field invalid", "%Q", jpathnt2);
+ return MCPC_EC_JSON_FIELD_INVALID;
+}
+
+
+mcpc_errcode_t
+mcpc_conn_free (mcpc_conn_t *conn)
+{
+ bug_if_nullptr (conn);
+
+ if (conn->cname != nullptr)
+ {
+ free ((void *) conn->cname);
+ conn->cname = nullptr;
+ conn->cname_len = 0;
+ }
+
+ if (conn->nex != nullptr)
+ {
+ mcpc_conn_free (conn->nex);
+ }
+
+ free (conn);
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_connpool_free (mcpc_connpool_t *connpool)
+{
+ bug_if_nullptr (connpool);
+ mcpc_conn_free (connpool->head);
+ free (connpool);
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_server_t *
+mcpc_server_new (mcpc_server_type_t typ, mcpc_toolpool_t *toolpool, u32_t vabeg, ...)
+{
+ mcpc_assert (typ != MCPC_SV_NONE, MCPC_EC_BUG);
+ mcpc_assert (toolpool != nullptr, MCPC_EC_BUG);
+
+ mcpc_server_t *sv = (mcpc_server_t *) mcpc_alloc (sizeof (mcpc_server_t));
+
+ sv->typ = typ;
+ sv->toolpool = toolpool;
+ sv->prmptpool = mcpc_prmptpool_new ();
+ sv->rscpool = mcpc_rscpool_new ();
+ sv->rpcres = _new_mjson_fixedbuf ();
+ sv->connpool = mcpc_alloc (sizeof (mcpc_connpool_t));
+ _connpool_init ((mcpc_connpool_t *) sv->connpool);
+
+ if (false)
+ ;
+ else if (typ == MCPC_SV_IOSTRM)
+ {
+ va_list ap;
+ va_start (ap, vabeg);
+ FILE *strm0 = va_arg (ap, FILE *);
+ mcpc_assert (strm0 != nullptr, MCPC_EC_BUG);
+ FILE *strm1 = va_arg (ap, FILE *);
+ mcpc_assert (strm1 != nullptr, MCPC_EC_BUG);
+ mcpc_conn_t *conn0 = mcpc_conn_new (MCPC_CONN_STRM, strm0);
+ mcpc_assert (MCPC_EC_0 == mcpc_connpool_addref ((mcpc_connpool_t *) sv->connpool, conn0), MCPC_EC_BUG);
+ mcpc_conn_t *conn1 = mcpc_conn_new (MCPC_CONN_STRM, strm1);
+ mcpc_assert (MCPC_EC_0 == mcpc_connpool_addref ((mcpc_connpool_t *) (sv->connpool), conn1), MCPC_EC_BUG);
+ va_end (ap);
+ }
+ else if (typ == MCPC_SV_TCP)
+ {
+ }
+ else
+ {
+ mcpc_assert (false, MCPC_EC_BUG);
+ }
+
+ ch8_t *default_name = u8c1 ("untitled");
+ u8_t default_name_len = (u8_t) u8strlen (default_name);
+ sv->name = mcpc_alloc ((size_t) default_name_len);
+ memcpy ((void *) sv->name, default_name, default_name_len);
+ sv->name_len = default_name_len;
+
+ ch8_t *default_ver = u8c1 ("0.1");
+ u8_t default_ver_len = (u8_t) u8strlen (default_ver);
+ sv->ver = mcpc_alloc ((size_t) default_ver_len);
+ memcpy ((void *) sv->ver, default_ver, default_ver_len);
+ sv->ver_len = default_ver_len;
+
+ sv->capa = 0;
+
+ return sv;
+}
+
+MCPC_API mcpc_server_t *
+mcpc_server_new_iostrm (const FILE *const strm_in, const FILE *const strm_out)
+{
+ mcpc_assert (strm_in != nullptr, MCPC_EC_BUG);
+ mcpc_assert (strm_out != nullptr, MCPC_EC_BUG);
+
+ mcpc_toolpool_t *toolpool = mcpc_toolpool_new ();
+ mcpc_server_t *sv = mcpc_server_new (MCPC_SV_IOSTRM, toolpool, true, strm_in, strm_out);
+
+ return sv;
+}
+
+MCPC_API mcpc_server_t *
+mcpc_server_new_tcp ()
+{
+ mcpc_toolpool_t *toolpool = mcpc_toolpool_new ();
+ mcpc_server_t *sv = mcpc_server_new (MCPC_SV_TCP, toolpool, false);
+
+ return sv;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_set_name (mcpc_server_t *sv, const char8_t *name, size_t name_len)
+{
+ bug_if (name_len > (size_t) UINT8_MAX);
+ sv->name = mcpc_realloc ((void *) sv->name, name_len);
+ memcpy ((void *) sv->name, name, name_len);
+ sv->name_len = (u8_t) name_len;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_set_nament (mcpc_server_t *sv, const char8_t *nament)
+{
+ return mcpc_server_set_name (sv, nament, u8strlen (nament));
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_prmt (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_PRMT;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_prmt_listchg (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_PRMT_LISTCHG;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_tool (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_TOOL;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_tool_listchg (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_TOOL_LISTCHG;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_rsc (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_RSC;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_rsc_listchg (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_RSC_LISTCHG;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_rsc_subscr (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_RSC_SUBSRC;
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_capa_enable_complt (mcpc_server_t *sv)
+{
+ sv->capa |= MCPC_CAPA_COMPLT;
+ return MCPC_EC_0;
+}
+
+static inline bool
+_server_is_buggy (const mcpc_server_t *sv)
+{
+ if (sv == nullptr)
+ return true;
+ if (sv->typ == MCPC_SV_NONE)
+ return true;
+ if (sv->toolpool == nullptr)
+ return true;
+
+ return false;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_add_tool (mcpc_server_t *sv, mcpc_tool_t *tool)
+{
+ mcpc_tcallcb_t callback = nullptr;
+ if (_server_is_buggy (sv))
+ return MCPC_EC_BUG;
+ if (tool == nullptr)
+ return MCPC_EC_BUG;
+ if (mcpc_tool_get_call_cb (tool, &callback) != MCPC_EC_0 || callback == nullptr)
+ return MCPC_EC_BUG;
+ mcpc_toolpool_addfre ((mcpc_toolpool_t *) sv->toolpool, tool);
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_add_prmpt (mcpc_server_t *sv, mcpc_prmpt_t *prmpt)
+{
+ if (_server_is_buggy (sv))
+ return MCPC_EC_BUG;
+ if (prmpt == nullptr)
+ return MCPC_EC_BUG;
+ mcpc_prmptpool_addfre ((mcpc_prmptpool_t *) sv->prmptpool, prmpt);
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_add_rsc (mcpc_server_t *sv, mcpc_rsc_t *rsc)
+{
+ if (_server_is_buggy (sv))
+ return MCPC_EC_BUG;
+ if (rsc == nullptr)
+ return MCPC_EC_BUG;
+ mcpc_rscpool_addfre ((mcpc_rscpool_t *) sv->rscpool, rsc);
+ return MCPC_EC_0;
+}
+
+bool
+mcpc_server_supp_prompts (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_PRMT);
+}
+
+bool
+mcpc_server_supp_prompts_lschg (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_PRMT_LISTCHG) == MCPC_CAPA_PRMT_LISTCHG;
+}
+
+bool
+mcpc_server_supp_tools (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_TOOL) == MCPC_CAPA_TOOL;
+}
+
+bool
+mcpc_server_supp_tools_lschg (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_TOOL_LISTCHG) == MCPC_CAPA_TOOL_LISTCHG;
+}
+
+bool
+mcpc_server_supp_rscs (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_RSC) == MCPC_CAPA_RSC;
+}
+
+bool
+mcpc_server_supp_rscs_lschg (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_RSC_LISTCHG) == MCPC_CAPA_RSC_LISTCHG;
+}
+
+bool
+mcpc_server_supp_rscs_subs (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_RSC_SUBSRC) == MCPC_CAPA_RSC_SUBSRC;
+}
+
+bool
+mcpc_server_supp_complt (const mcpc_server_t *sv)
+{
+ return (sv->capa & MCPC_CAPA_COMPLT) == MCPC_CAPA_COMPLT;
+}
+
+
+// ----------------------------- iostrm -----------------------------
+
+mcpc_errcode_t
+_wait_iostrm (mcpc_server_t *sv)
+{
+ mcpc_conn_t *conn0 = mcpc_connpool_getconn (sv->connpool, 0);
+ mcpc_assert (conn0->typ == MCPC_CONN_STRM, MCPC_EC_BUG);
+ mcpc_conn_t *conn1 = mcpc_connpool_getconn (sv->connpool, 1);
+ mcpc_assert (conn1->typ == MCPC_CONN_STRM, MCPC_EC_BUG);
+
+ FILE *in_strm = mcpc_conn_getstrm (conn0);
+ mcpc_assert (in_strm != nullptr, MCPC_EC_BUG);
+ FILE *out_strm = mcpc_conn_getstrm (conn1);
+ mcpc_assert (out_strm != nullptr, MCPC_EC_BUG);
+
+ size_t rbuf_cap = BUFCAP_INIT_JSONREQ;
+ size_t rbuf_len = 0;
+ char *rbuf = mcpc_alloc (rbuf_cap);
+ int ch;
+ if (rbuf == nullptr)
+ return MCPC_EC_BUFCAP;
+
+ while ((ch = getc (in_strm)) != EOF)
+ {
+ if (ch == '\n')
+ {
+ if (rbuf_len > 0 && rbuf[rbuf_len - 1] == '\r')
+ rbuf_len -= 1;
+ if (rbuf_len > 0)
+ handle_inone (sv, rbuf, rbuf_len, (mcpc_sock_t) - 1);
+ rbuf_len = 0;
+ continue;
+ }
+ if (rbuf_len == rbuf_cap)
+ {
+ char *grown;
+ if (rbuf_cap > SIZE_MAX / 2)
+ {
+ free (rbuf);
+ return MCPC_EC_BUFCAP;
+ }
+ grown = mcpc_realloc (rbuf, rbuf_cap * 2);
+ if (grown == nullptr)
+ {
+ free (rbuf);
+ return MCPC_EC_BUFCAP;
+ }
+ rbuf = grown;
+ rbuf_cap *= 2;
+ }
+ rbuf[rbuf_len++] = (char) ch;
+ }
+ if (ferror (in_strm))
+ {
+ free (rbuf);
+ return MCPC_EC_BUG;
+ }
+ if (rbuf_len > 0)
+ handle_inone (sv, rbuf, rbuf_len, (mcpc_sock_t) - 1);
+ free (rbuf);
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+_svimpl_iostrm (mcpc_server_t *sv)
+{
+ mcpc_assert (sv->connpool->len == 2, MCPC_EC_BUG);
+ jsonrpc_init (nullptr, nullptr); // no need to handle responses
+
+ return _wait_iostrm (sv);
+}
+
+mcpc_errcode_t
+_start_iostrm (mcpc_server_t *sv)
+{
+ return _svimpl_iostrm (sv);
+}
+
+// ------------------------------ tcp ------------------------------
+
+typedef struct
+{
+ mcpc_sock_t csock;
+ struct sockaddr_in caddr;
+ mcpc_server_t *sv;
+} accept_hdlr_args_t;
+
+mcpc_errcode_t
+_new_tcp_listen (mcpc_sock_t *ssock)
+{
+ mcpc_sock_t tmpsock = (mcpc_sock_t) 0;
+ struct sockaddr_in saddr;
+
+ tmpsock = socket (AF_INET, SOCK_STREAM, 0);
+#if is_unix
+ if (tmpsock < 0)
+#endif
+#if is_win
+ if (tmpsock == INVALID_SOCKET)
+#endif
+ {
+ return MCPC_EC_SVIMPL_SOCKET;
+ }
+
+ int level = SOL_SOCKET;
+ int optname = SO_REUSEADDR;
+ int optval = 1;
+ mcpc_assert (0 == setsockopt (tmpsock, level, optname, (const char *) &optval, sizeof (optval)), MCPC_EC_BUG);
+
+ saddr.sin_family = AF_INET;
+ saddr.sin_port = htons (PORT);
+#if defined(is_unix)
+ inet_pton (AF_INET, "127.0.0.1", &saddr.sin_addr);
+#elif defined(is_win)
+ saddr.sin_addr.s_addr = inet_addr ("127.0.0.1");
+#endif
+
+ if (bind (tmpsock, (struct sockaddr *) &saddr, sizeof (saddr)) == -1)
+ {
+ return MCPC_EC_SVIMPL_BIND;
+ }
+
+ if (listen (tmpsock, 16) == -1)
+ {
+ return MCPC_EC_SVIMPL_LISTEN;
+ }
+
+ *ssock = tmpsock;
+
+ return MCPC_EC_0;
+}
+
+#ifdef is_unix
+static void *
+#endif
+#ifdef is_win
+ DWORD WINAPI
+#endif
+_wait_conn (void *args)
+{
+ accept_hdlr_args_t *thread_args_ptr = (accept_hdlr_args_t *) args;
+ mcpc_sock_t csock = thread_args_ptr->csock;
+#ifdef MCPC_DBG
+ struct sockaddr_in caddr = thread_args_ptr->caddr;
+#endif
+ mcpc_server_t *sv = thread_args_ptr->sv;
+
+ tulog_d ("New connection from client IP address %s and port %d", inet_ntoa (caddr.sin_addr), ntohs (caddr.sin_port));
+
+ char ch;
+ int n_brace = 0;
+ bool start_read = false;
+ bool enter_str = false;
+ size_t rbuf_cap = BUFCAP_INIT_JSONREQ;
+ char *rbuf = mcpc_alloc (rbuf_cap);
+ char *rbuf_p = rbuf;
+ i32_t last_nrecv = 0;
+ char last_read = 0;
+ char lastlast_read = 0;
+
+ while (true)
+ {
+ last_nrecv = recv (csock, &ch, 1, 0);
+ if (false)
+ ;
+ else if (last_nrecv == 0)
+ {
+ break;
+ }
+ else if (last_nrecv < 0)
+ {
+ tulog_d ("recv err %s", strerror (errno));
+ break;
+ }
+
+ if (!enter_str && ch == CH_LBRC)
+ {
+ n_brace += 1;
+ if (!start_read)
+ start_read = true;
+ }
+
+ if (!start_read)
+ {
+ continue;
+ }
+
+ if (start_read)
+ {
+ *rbuf_p = ch;
+ rbuf_p += 1;
+ lastlast_read = last_read;
+ last_read = ch;
+ if (rbuf_p - rbuf >= (ptrdiff_t) rbuf_cap)
+ {
+ rbuf_cap += rbuf_cap;
+ rbuf = mcpc_realloc (rbuf, rbuf_cap);
+ }
+ }
+
+ if (!enter_str && last_read == CH_RBRC)
+ {
+ n_brace -= 1;
+ if (n_brace == 0)
+ {
+ ptrdiff_t nread = rbuf_p - rbuf;
+ rbuf[nread - 1] = CH_RBRC;
+ rbuf[nread] = 0x00;
+ handle_inone (sv, rbuf, (size_t) nread, csock);
+ rbuf_p = rbuf;
+ start_read = false;
+ enter_str = false;
+ tulog_d ("one msg handled");
+ }
+ }
+ if (last_read == CH_QUO && lastlast_read != CH_BSLASH)
+ {
+ enter_str = !enter_str;
+ }
+ }
+
+ free (rbuf);
+
+#ifdef is_unix
+ close (csock);
+#endif
+#ifdef is_win
+ closesocket (csock);
+#endif
+
+ free (thread_args_ptr);
+
+#ifdef is_unix
+ tulog_d ("thread %lu exiting...", pthread_self ());
+#endif
+#ifdef is_win
+ tulog_d ("thread %lu exiting...", GetCurrentThreadId ());
+#endif
+
+#ifdef is_unix
+ pthread_exit (nullptr);
+#endif
+#ifdef is_win
+ ExitThread (0);
+#endif
+}
+
+mcpc_errcode_t
+_svimpl_tcp (mcpc_server_t *sv)
+{
+
+#ifdef is_win
+ WSADATA wsaData;
+ if (WSAStartup (MAKEWORD (2, 2), &wsaData) != 0)
+ {
+ return MCPC_EC_SVIMPL_SOCKET;
+ }
+#endif
+
+ mcpc_sock_t ssock;
+ struct sockaddr_in caddr;
+ socklen_t caddr_length = sizeof (caddr);
+
+ mcpc_assert (MCPC_EC_0 == _new_tcp_listen (&ssock), MCPC_EC_BUG);
+
+ tulog_d ("Server listening on port %d...", PORT);
+
+ mcpc_sock_t csock;
+ while (true)
+ {
+ csock = accept (ssock, (struct sockaddr *) &caddr, &caddr_length);
+#ifdef is_unix
+ if (csock < 0)
+#endif
+#ifdef is_win
+ if (csock == INVALID_SOCKET)
+#endif
+ {
+ tulog_d ("accept failed");
+ continue;
+ }
+
+ accept_hdlr_args_t *args = (accept_hdlr_args_t *) mcpc_alloc (sizeof (accept_hdlr_args_t));
+ args->csock = csock;
+ args->caddr = caddr;
+ args->sv = sv;
+
+#ifdef is_unix
+ pthread_t thread;
+ if (pthread_create (&thread, nullptr, _wait_conn, args) != 0)
+ {
+ tulog_d ("thread creation failed");
+ free (args);
+ close (csock);
+ }
+#endif
+#ifdef is_win
+ HANDLE thread;
+ DWORD threadId;
+ thread = CreateThread (NULL, 0, _wait_conn, args, 0, &threadId);
+ if (thread == NULL)
+ {
+ tulog_d ("thread creation failed");
+ free (args);
+ closesocket (csock);
+ }
+#endif
+ }
+
+#ifdef is_win
+ WSACleanup ();
+#endif
+
+ return 0;
+}
+
+mcpc_errcode_t
+_start_tcp (mcpc_server_t *sv)
+{
+ return _svimpl_tcp (sv);
+}
+
+// ------------------------------- - -------------------------------
+
+MCPC_API mcpc_errcode_t
+mcpc_server_start (mcpc_server_t *sv)
+{
+ if (_server_is_buggy (sv))
+ {
+ return MCPC_EC_BUG;
+ }
+ if (false)
+ ;
+ else if (sv->typ == MCPC_SV_IOSTRM)
+ {
+ return _start_iostrm (sv);
+ }
+ else if (sv->typ == MCPC_SV_TCP)
+ {
+ return _start_tcp (sv);
+ }
+ else
+ {
+ return MCPC_EC_BUG;
+ }
+ return MCPC_EC_0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_server_close (mcpc_server_t *sv)
+{
+ // return MCPC_EC_0; // NOTE: this does not trigger asan but valgrind
+ if (_server_is_buggy (sv))
+ {
+ return MCPC_EC_BUG;
+ }
+
+ if (sv->name != nullptr)
+ {
+ free ((void *) sv->name);
+ sv->name = nullptr;
+ sv->name_len = 0;
+ }
+
+ if (sv->ver != nullptr)
+ {
+ free ((void *) sv->ver);
+ sv->ver = nullptr;
+ sv->ver_len = 0;
+ }
+
+ free (sv->rpcres.ptr);
+ mcpc_toolpool_free ((mcpc_toolpool_t *) sv->toolpool);
+ mcpc_rscpool_free ((mcpc_rscpool_t *) sv->prmptpool);
+ mcpc_rscpool_free ((mcpc_rscpool_t *) sv->rscpool);
+ mcpc_connpool_free ((mcpc_connpool_t *) sv->connpool);
+
+ free (sv);
+
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_server_cleanup_rpcres (mcpc_server_t *sv)
+{
+ sv->rpcres.len = 0;
+ return MCPC_EC_0;
+}
+
+bool
+_connpool_is_addable (mcpc_connpool_t *connpool)
+{
+ if (connpool->len < connpool->cap)
+ {
+ return true;
+ }
+ return false;
+}
+
+mcpc_conn_t *
+mcpc_conn_new (mcpc_conn_type_t typ, void *back)
+{
+ mcpc_conn_t *conn = mcpc_alloc (sizeof (mcpc_conn_t));
+ conn->typ = typ;
+
+ if (false)
+ ;
+ else if (typ == MCPC_CONN_STRM)
+ {
+ FILE *strm = (FILE *) back;
+ conn->strm = strm;
+ }
+ else if (typ == MCPC_CONN_FD)
+ {
+ int fd = *((int *) back);
+ conn->fd = fd;
+ }
+ else
+ {
+ conn->strm = nullptr;
+ }
+ conn->cname = nullptr;
+ conn->cname_len = 0;
+ conn->client_init = MCPC_INIT_NONE;
+ conn->nex = nullptr;
+
+ return conn;
+}
+
+FILE *
+mcpc_conn_getstrm (mcpc_conn_t *conn)
+{
+ if (conn->typ != MCPC_CONN_STRM)
+ {
+ return nullptr;
+ }
+ return conn->strm;
+}
+
+int
+mcpc_conn_getfd (mcpc_conn_t *conn)
+{
+ if (conn->typ != MCPC_CONN_FD)
+ {
+ return 0;
+ }
+ return conn->fd & ((1ULL << (sizeof (int) * 8)) - 1);
+}
+
+mcpc_errcode_t
+mcpc_connpool_addref (mcpc_connpool_t *connpool, mcpc_conn_t *conn)
+{
+ if (_connpool_is_buggy ((const mcpc_connpool_t *) connpool))
+ {
+ return MCPC_EC_BUG;
+ }
+
+ if (!_connpool_is_addable (connpool))
+ {
+ return MCPC_EC_CONNPOOL_NOTADD;
+ }
+
+ if (connpool->head == nullptr)
+ {
+ connpool->head = conn;
+ connpool->len = 1;
+ }
+ else
+ {
+ mcpc_conn_t *cur_conn = connpool->head;
+ while (cur_conn->nex != nullptr)
+ {
+ cur_conn = cur_conn->nex;
+ }
+ cur_conn->nex = conn;
+ connpool->len += 1;
+ // mcpc_assert(!ckd_add(&connpool->len, connpool->len, 1), MCPC_EC_BUG);
+ }
+
+ return MCPC_EC_0;
+}
+
+mcpc_conn_t *
+mcpc_connpool_getconn (const mcpc_connpool_t *connpool, const mcpc_connpool_size_t idx)
+{
+ mcpc_assert (!_connpool_is_buggy (connpool), MCPC_EC_BUG);
+ mcpc_connpool_size_t cur_idx = 0;
+ mcpc_conn_t *cur_conn = connpool->head;
+ while (cur_conn != nullptr && cur_idx < idx)
+ {
+ cur_conn = cur_conn->nex;
+ cur_idx += 1;
+ // mcpc_assert(!ckd_add(&cur_idx, cur_idx, 1), MCPC_EC_BUG);
+ }
+ return cur_conn;
+}
+
+mcpc_conn_t *
+mcpc_connpool_find_by_sock (const mcpc_connpool_t *connpool, mcpc_sock_t sock)
+{
+ mcpc_assert (!_connpool_is_buggy (connpool), MCPC_EC_BUG);
+
+ // iostrm
+ if (mcpc_sock_invalid (sock))
+ {
+ return connpool->head;
+ }
+
+ // tcp
+ mcpc_conn_t *cur_conn = connpool->head;
+ while (cur_conn != nullptr)
+ {
+ mcpc_sock_t cur_sock = cur_conn->fd;
+ if (cur_sock == sock)
+ {
+ return cur_conn;
+ }
+ cur_conn = cur_conn->nex;
+ }
+
+ return nullptr;
+}
+
+mcpc_errcode_t
+mcpc_conn_set_cname (mcpc_conn_t *conn, const ch8_t *const cname, const u8_t cname_len)
+{
+ void *hp = mcpc_alloc (cname_len);
+ memcpy (hp, cname, cname_len);
+ conn->cname = hp;
+ conn->cname_len = cname_len;
+ return MCPC_EC_0;
+}
+
+void
+_handle_initbeg (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock)
+{
+
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init == MCPC_INIT_SUCC)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "conn already initialized", NULL);
+ goto output_res;
+ }
+
+ // - - -
+
+ const char8_t *protover = nullptr;
+ size_t protover_len = 0;
+ if (MCPC_EC_0 != _tulc_jparams_getref_field_u8str_q (r, u8c1 ("$.protocolVersion"), &protover, &protover_len))
+ return;
+
+ // - - - client info
+
+ char8_t *cinfo_name = nullptr;
+ size_t cinfo_name_len = 0;
+ char8_t *cinfo_ver = nullptr;
+ size_t cinfo_ver_len = 0;
+
+ cinfo_name = alloca (128);
+ if (MCPC_EC_0 !=
+ _tulc_jparams_get_field_u8str (r, u8c1 ("$.clientInfo.name"), &cinfo_name, &cinfo_name_len, 128, true))
+ return; // TODO P5 add test
+
+ cinfo_ver = alloca (128);
+ if (MCPC_EC_0 !=
+ _tulc_jparams_get_field_u8str (r, u8c1 ("$.clientInfo.version"), &cinfo_ver, &cinfo_ver_len, 128, true))
+ return; // TODO P5 add test
+
+ mcpc_conn_set_cname (conn, cinfo_name, (u8_t) cinfo_name_len);
+
+ // - - - capabilities
+
+ const char8_t *ccapas = nullptr;
+ size_t ccapas_len = 0;
+ if (MCPC_EC_0 != _tulc_jparams_getref_field_obj (r, u8c1 ("$.capabilities"), &ccapas, &ccapas_len))
+ return; // TODO P5 add test
+
+ // TODO P6 capabilities negotiation
+
+ char *buftpool = nullptr;
+ size_t buftpool_len = 0;
+ const size_t buftpool_cap = 65536;
+ buftpool = mcpc_alloc (buftpool_cap);
+ retbuf_t rtbf = { buftpool_cap, buftpool, &buftpool_len };
+ mcpc_assert (!serlz_initres (rtbf, sv), MCPC_EC_BUG);
+ mcpc_assert (buftpool != nullptr, MCPC_EC_BUG);
+ mcpc_assert (buftpool_len > 0, MCPC_EC_BUG);
+ jsonrpc_return_success2 (r, mcpc_sock_invalid (csock), "%.*s", buftpool_len, buftpool);
+ free (buftpool); // TODO intn_free
+
+ conn->client_init = MCPC_INIT_BEG;
+
+output_res:
+ return;
+}
+
+void
+_handle_initdone (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock, bool *noreply)
+{
+
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init != MCPC_INIT_BEG)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "client not begin init", NULL);
+ goto output_res;
+ }
+
+ conn->client_init = MCPC_INIT_SUCC;
+ *noreply = true;
+
+output_res:
+ return;
+}
+
+void
+_handle_tools_list (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock)
+{
+
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init != MCPC_INIT_SUCC)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "client not initialized", NULL);
+ goto output_res;
+ }
+
+ char *buftpool = nullptr;
+ size_t buftpool_len = 0;
+ const size_t buftpool_cap = 65536;
+ buftpool = mcpc_alloc (buftpool_cap);
+ retbuf_t rtbf = { buftpool_cap, buftpool, &buftpool_len };
+ mcpc_assert (!serlz_toolpool (rtbf, sv->toolpool), MCPC_EC_BUG);
+ mcpc_assert (buftpool != nullptr, MCPC_EC_BUG);
+ mcpc_assert (buftpool_len > 0, MCPC_EC_BUG);
+ jsonrpc_return_success2 (r, mcpc_sock_invalid (csock), "%.*s", buftpool_len, buftpool);
+ free (buftpool); // TODO intn_free
+
+output_res:
+ return;
+}
+
+void
+_handle_prmpt_list (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock)
+{
+ if (!mcpc_server_supp_prompts (sv))
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "prompts is not supported", NULL);
+ goto output_res;
+ }
+
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init != MCPC_INIT_SUCC)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "client not initialized", NULL);
+ goto output_res;
+ }
+
+ char *buftpool = nullptr;
+ size_t buftpool_len = 0;
+ const size_t buftpool_cap = 1024;
+ buftpool = mcpc_alloc (buftpool_cap);
+ retbuf_t rtbf = { buftpool_cap, buftpool, &buftpool_len };
+ mcpc_assert (!serlz_prmptpool (rtbf, sv->prmptpool), MCPC_EC_BUG);
+ mcpc_assert (buftpool != nullptr, MCPC_EC_BUG);
+ mcpc_assert (buftpool_len > 0, MCPC_EC_BUG);
+ jsonrpc_return_success2 (r, mcpc_sock_invalid (csock), "%.*s", buftpool_len, buftpool);
+ free (buftpool); // TODO intn_free
+
+output_res:
+ return;
+}
+
+void
+_handle_lsrsc (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock)
+{
+
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init != MCPC_INIT_SUCC)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "client not initialized", NULL);
+ goto output_res;
+ }
+
+ char *buftpool = nullptr;
+ size_t buftpool_len = 0;
+ const size_t buftpool_cap = 1024;
+ buftpool = mcpc_alloc (buftpool_cap);
+ retbuf_t rtbf = { buftpool_cap, buftpool, &buftpool_len };
+ mcpc_assert (!serlz_rscpool (rtbf, sv->rscpool), MCPC_EC_BUG);
+ mcpc_assert (buftpool != nullptr, MCPC_EC_BUG);
+ mcpc_assert (buftpool_len > 0, MCPC_EC_BUG);
+ jsonrpc_return_success2 (r, mcpc_sock_invalid (csock), "%.*s", buftpool_len, buftpool);
+ free (buftpool); // TODO intn_free
+
+output_res:
+ return;
+}
+
+void
+_handle_tools_call (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock)
+{
+ mcpc_ucbr_t *ucbr = nullptr;
+ char *buftpool = nullptr;
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init != MCPC_INIT_SUCC)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "client not initialized", NULL);
+ goto output_res;
+ }
+
+ char8_t *callname = nullptr; // TODO suff?
+ size_t callname_len = 0;
+
+ callname = alloca (1024); // TODO insuffi?
+ if (MCPC_EC_0 != _tulc_jparams_get_field_u8str (r, u8c1 ("$.name"), &callname, &callname_len, 1024, true))
+ return;
+
+ // const mcpc_tool_t *tool = mcpc_toolpool_find_tool (sv->toolpool, callname, callname_len);
+
+ const mcpc_tool_t *tool = (const mcpc_tool_t *) mcpc_toolpool_getbyname (sv->toolpool, callname, callname_len);
+ if (tool == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "tool not found", NULL);
+ goto output_res;
+ }
+
+ const char8_t *callargs = nullptr;
+ size_t callargs_len = 0;
+ if (MCPC_EC_0 != _tulc_jparams_getref_field_obj (r, u8c1 ("$.arguments"), &callargs, &callargs_len))
+ goto output_res;
+
+ size_t property_count = mcpc_tool_get_toolprop_len (tool);
+ for (size_t property_index = 0; property_index < property_count; ++property_index)
+ {
+ mcpc_toolprop_t *cur_tprop
+ = (mcpc_toolprop_t *) mcpc_tool_get_toolprop (tool, property_index);
+ const char8_t *cur_tp_name = nullptr;
+ size_t cur_tp_name_len = 0;
+ bug_if_nz (mcpc_toolprop_getref_name (cur_tprop, &cur_tp_name, &cur_tp_name_len));
+ mcpc_anytype_t cur_tp_type = MCPC_NONE;
+ bug_if_nz (mcpc_toolprop_get_data_type (cur_tprop, &cur_tp_type));
+ char8_t *cur_tp_jpath = alloca (cur_tp_name_len + 3);
+ const char *propval_q = nullptr;
+ int propval_q_len = 0;
+ int propval_type;
+ snprintf ((char *) cur_tp_jpath, cur_tp_name_len + 3, "$.%.*s", (int) cur_tp_name_len, cur_tp_name);
+ cur_tprop->flags &= ~ANYDATA_FL_PRESENT;
+ propval_type = mjson_find ((const char *) callargs, (int) callargs_len,
+ (const char *) cur_tp_jpath, &propval_q, &propval_q_len);
+ if (propval_type == MJSON_TOK_INVALID)
+ continue;
+ if (false)
+ ;
+ else if (cur_tp_type == MCPC_I32)
+ {
+ double propval = 0;
+ if (propval_type != MJSON_TOK_NUMBER
+ || mjson_get_number (propval_q, propval_q_len, "$", &propval) != 1 || !isfinite (propval)
+ || trunc (propval) != propval || propval < INT32_MIN || propval > INT32_MAX)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "integer argument is invalid", "%.*Q",
+ cur_tp_name_len, cur_tp_name);
+ goto output_res;
+ }
+ mcpc_toolprop_set_i32 (cur_tprop, (i32_t) propval);
+ }
+ else if (cur_tp_type == MCPC_U8STR)
+ {
+ char8_t *propval;
+ int propval_len;
+ if (propval_type != MJSON_TOK_STRING)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "string argument is invalid", "%.*Q",
+ cur_tp_name_len, cur_tp_name);
+ goto output_res;
+ }
+ propval = mcpc_alloc ((size_t) propval_q_len + 1);
+ if (propval == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "out of memory", NULL);
+ goto output_res;
+ }
+ propval_len = mjson_get_string (propval_q, propval_q_len, "$", (char *) propval,
+ (size_t) propval_q_len + 1);
+ if (propval_len < 0)
+ {
+ free (propval);
+ jsonrpc_return_error (r, JSONRPC_ERROR_BAD_PARAMS, "string argument is invalid", NULL);
+ goto output_res;
+ }
+ mcpc_toolprop_set_u8str (cur_tprop, propval, (size_t) propval_len);
+ free (propval);
+ }
+ else
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "unsupported argument type", NULL);
+ goto output_res;
+ }
+ }
+
+ ucbr = mcpc_ucbr_new ();
+ if (ucbr == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "out of memory", NULL);
+ goto output_res;
+ }
+
+ mcpc_tcallcb_t callcb = nullptr;
+ if (mcpc_tool_get_call_cb (tool, &callcb) != MCPC_EC_0 || callcb == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "tool callback is unavailable", NULL);
+ goto output_res;
+ }
+ callcb (tool, ucbr);
+
+ size_t buftpool_len = 0;
+ const size_t buftpool_cap = mcpc_ucbr_get_serlz_lenhint (ucbr);
+ if (buftpool_cap == 0)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "response is too large", NULL);
+ goto output_res;
+ }
+ buftpool = mcpc_alloc (buftpool_cap);
+ if (buftpool == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "out of memory", NULL);
+ goto output_res;
+ }
+ retbuf_t rtbf = { buftpool_cap, buftpool, &buftpool_len };
+
+ serlz_tool_callres (rtbf, ucbr);
+ if (buftpool_len == 0 || buftpool_len >= buftpool_cap)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "response serialization failed", NULL);
+ goto output_res;
+ }
+
+ if (!ensure_buf_suffi (&sv->rpcres, buftpool_cap))
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_INTERNAL, "response buffer allocation failed", NULL);
+ goto output_res;
+ }
+ jsonrpc_return_success2 (r, mcpc_sock_invalid (csock), "%.*s", buftpool_len, buftpool);
+
+output_res:
+ if (ucbr != nullptr)
+ mcpc_ucbr_free (ucbr);
+ free (buftpool);
+ return;
+}
+
+void
+_handle_prmpt_call (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock)
+{
+ if (!mcpc_server_supp_prompts (sv))
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "prompts is not supported", NULL);
+ goto output_res;
+ }
+
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init != MCPC_INIT_SUCC)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "client not initialized", NULL);
+ goto output_res;
+ }
+
+ char8_t *callname = nullptr;
+ size_t callname_len = 0;
+
+ callname = alloca (128); // TODO insuffi?
+ if (MCPC_EC_0 != _tulc_jparams_get_field_u8str (r, u8c1 ("$.name"), &callname, &callname_len, 1024, true))
+ return;
+
+ bug_if (callname_len == 0);
+
+ const mcpc_prmpt_t *prmpt = (const mcpc_prmpt_t *) mcpc_prmptpool_getbyname (sv->prmptpool, callname, callname_len);
+ if (prmpt == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "prmopt not found", NULL);
+ goto output_res;
+ }
+
+
+ const char8_t *callargs = nullptr;
+ size_t callargs_len = 0;
+ callargs = alloca (1024); // TODO insuffi?
+ if (MCPC_EC_0 != _tulc_jparams_getref_field_obj (r, u8c1 ("$.arguments"), &callargs, &callargs_len))
+ return;
+
+ const mcpc_prmptarg_t *cur_parg = nullptr;
+ while ((cur_parg = mcpc_prmpt_get_next_prmptarg (prmpt)))
+ {
+ const char8_t *cur_tp_name = nullptr;
+ size_t cur_tp_name_len = 0;
+ bug_if_nz (mcpc_prmptarg_getref_name (cur_parg, &cur_tp_name, &cur_tp_name_len));
+ mcpc_anytype_t cur_tp_type = MCPC_NONE;
+ bug_if_nz (mcpc_prmptarg_get_data_type (cur_parg, &cur_tp_type));
+ char8_t *cur_tp_jpath = alloca (100);
+ sprintf ((char *) cur_tp_jpath, "$.%.*s", (int) cur_tp_name_len, cur_tp_name);
+ if (false)
+ ;
+ else if (cur_tp_type == MCPC_U8STR)
+ {
+ char8_t *propval = alloca (1024); // TODO might be insuff
+ size_t propval_len = 0;
+
+ // copy is essential here
+ if (MCPC_EC_0 !=
+ _tulc_jstr_get_field_u8str (r, callargs, callargs_len, cur_tp_jpath, &propval, &propval_len, 1024, true))
+ return;
+
+ mcpc_prmptarg_set_u8str ((mcpc_prmptarg_t *) cur_parg, propval, (size_t) propval_len);
+ }
+ }
+
+ mcpc_ucbr_t *ucbr = mcpc_ucbr_new ();
+
+ mcpc_prmpt_callcb_t callcb = nullptr;
+ mcpc_prmpt_get_callcb (prmpt, &callcb);
+ callcb (prmpt, ucbr);
+
+ char *buftpool = nullptr;
+ size_t buftpool_len = 0;
+ const size_t buftpool_cap = 1024;
+ buftpool = mcpc_alloc (buftpool_cap);
+ retbuf_t rtbf = { buftpool_cap, buftpool, &buftpool_len };
+
+ serlz_prmpt_callres (rtbf, ucbr);
+ mcpc_assert (buftpool != nullptr, MCPC_EC_BUG);
+ mcpc_assert (buftpool_len > 0, MCPC_EC_BUG);
+
+ jsonrpc_return_success2 (r, mcpc_sock_invalid (csock), "%.*s", buftpool_len, buftpool);
+
+ mcpc_ucbr_free (ucbr);
+ free (buftpool); // TODO intn_free
+
+output_res:
+ return;
+}
+
+
+void
+_handle_complt_complt (mcpc_server_t *sv, struct jsonrpc_request *r, mcpc_sock_t csock)
+{
+ if (!mcpc_server_supp_complt (sv))
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "completion is not supported", NULL);
+ goto output_res;
+ }
+
+ mcpc_conn_t *conn = mcpc_connpool_find_by_sock (sv->connpool, csock);
+ if (conn == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "connection not found", NULL);
+ goto output_res;
+ }
+ if (conn->client_init != MCPC_INIT_SUCC)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "client not initialized", NULL);
+ goto output_res;
+ }
+
+
+ // ref.type
+ char8_t *compref_type = alloca (1024); // TODO suff?
+ size_t compref_type_len = 0;
+ if (MCPC_EC_0 != _tulc_jparams_get_field_u8str (r, u8c1 ("$.ref.type"), &compref_type, &compref_type_len, 1024, true))
+ return;
+
+ int askfor_what = -1;
+
+ if (false);
+ else if (0 == u8strncmp (compref_type, u8c1 ("ref/prompt"), compref_type_len))
+ askfor_what = 1;
+ else if (0 == u8strncmp (compref_type, u8c1 ("ref/resource"), compref_type_len))
+ askfor_what = 2;
+
+ if (askfor_what < 0)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "<ref.type> invalid", NULL);
+ return;
+ }
+
+ char8_t *compref_name = nullptr; // TODO suff?
+ size_t compref_name_len = 0;
+ char8_t *compref_uri = nullptr; // TODO suff?
+ size_t compref_uri_len = 0;
+ char8_t *comparg_name = nullptr; // TODO suff?
+ size_t comparg_name_len = 0;
+ char8_t *comparg_val = nullptr; // TODO suff?
+ size_t comparg_val_len = 0;
+
+ bool askfor_prmpt = askfor_what == 1;
+ bool askfor_resc = askfor_what == 2;
+
+ mcpc_complt_t *complt = nullptr;
+
+ if (false);
+ else if (askfor_prmpt)
+ {
+ // ref.name
+ compref_name = alloca (1024); // TODO insuffi?
+ if (MCPC_EC_0 !=
+ _tulc_jparams_get_field_u8str (r, u8c1 ("$.ref.name"), &compref_name, &compref_name_len, 1024, true))
+ return;
+
+ // argument.name
+ comparg_name = alloca (1024); // TODO insuffi?
+ if (MCPC_EC_0 !=
+ _tulc_jparams_get_field_u8str (r, u8c1 ("$.argument.name"), &comparg_name, &comparg_name_len, 1024, true))
+ return;
+
+ // argument.value
+ comparg_val = alloca (1024); // TODO insuffi?
+ if (MCPC_EC_0 !=
+ _tulc_jparams_get_field_u8str (r, u8c1 ("$.argument.value"), &comparg_val, &comparg_val_len, 1024, false))
+ return;
+
+ const mcpc_prmpt_t *tgt_prmpt = mcpc_prmptpool_getbyname (sv->prmptpool, compref_name, compref_name_len);
+ if (tgt_prmpt == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "prompt not found", "%.*Q", compref_name_len, compref_name);
+ return;
+ }
+
+ const mcpc_prmptarg_t *tgt_prmptarg = mcpc_prmpt_getbyname_prmptarg (tgt_prmpt, comparg_name, comparg_name_len);
+ if (tgt_prmptarg == nullptr)
+ {
+ jsonrpc_return_error (r, JSONRPC_ERROR_NOT_FOUND, "promptarg not found", "%.*Q", comparg_name_len,
+ comparg_name);
+ return;
+ }
+
+ complt = mcpc_complt_new_prmptarg (tgt_prmpt, comparg_name, comparg_name_len, comparg_val, comparg_val_len);
+ }
+ else if (askfor_resc)
+ {
+ // ref.uri mycomm: rubbish design
+ compref_uri = alloca (1024);
+ if (MCPC_EC_0 !=
+ _tulc_jparams_get_field_u8str (r, u8c1 ("$.ref.uri"), &compref_uri, &compref_uri_len, 1024, false))
+ return;
+ complt = mcpc_complt_new_rsc (sv->rscpool, compref_uri, compref_uri_len);
+ }
+
+ bug_if_nullptr (complt);
+
+ char *buftpool = nullptr;
+ size_t buftpool_len = 0;
+ const size_t buftpool_cap = 1024;
+ buftpool = mcpc_alloc (buftpool_cap);
+ retbuf_t rtbf = { buftpool_cap, buftpool, &buftpool_len };
+
+ serlz_complt (rtbf, complt);
+ mcpc_complt_free (complt);
+
+ // buftpool_len =
+ // sprintf (buftpool, " %.*s %.*s %.*s %.*s", (int) comparg_name_len, comparg_name, (int) comparg_val_len, comparg_val,
+ // (int) compref_type_len, compref_type, (int) compref_type_len, compref_type);
+ // strcpy (buftpool, "xxxxx");
+
+ mcpc_assert (buftpool != nullptr, MCPC_EC_BUG);
+ mcpc_assert (buftpool_len > 0, MCPC_EC_BUG);
+
+ jsonrpc_return_success2 (r, mcpc_sock_invalid (csock), "%.*s", buftpool_len, buftpool);
+
+ free (buftpool); // TODO intn_free
+
+output_res:
+ return;
+}
+
+bool
+is_metho_need_params (mcpc_metho_t metho)
+{
+ mcpc_assert (metho != MCPC_METHO_UNKNOWN, MCPC_EC_BUG);
+ if (metho == MCPC_METHO_INITBEG)
+ return true;
+ if (metho == MCPC_METHO_CALLTOOL)
+ return true;
+ if (metho == MCPC_METHO_PRMPT_CALL)
+ return true;
+ if (metho == MCPC_METHO_COMPLT_COMPLT)
+ return true;
+ return false;
+}
+
+void
+handle_inone (mcpc_server_t *sv, const char *const rbuf, size_t nread, mcpc_sock_t csock)
+{
+ bool noreply = false;
+ bool has_id = false;
+ char8_t *methodname = nullptr;
+ int32_t methodname_len = 0;
+ int parsed;
+ size_t offset;
+ tuflog_d ("rbuf:%.*s", nread, rbuf);
+ struct jsonrpc_request r = { 0 };
+ sv->rpcres.len = 0;
+ r.ctx = &jsonrpc_default_context;
+ r.frame = rbuf;
+ r.frame_len = (int) nread;
+ r.fn = mjson_print_fixed_buf;
+ r.fn_data = (void *) &sv->rpcres;
+
+ parsed = nread > INT_MAX ? MJSON_ERROR_INVALID_INPUT : mjson (rbuf, (int) nread, nullptr, nullptr);
+ if (parsed < 0)
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID, "parse error", NULL);
+ goto output_res;
+ }
+ for (offset = (size_t) parsed; offset < nread; ++offset)
+ if (rbuf[offset] != ' ' && rbuf[offset] != '\t' && rbuf[offset] != '\r')
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID, "parse error", NULL);
+ goto output_res;
+ }
+ offset = 0;
+ while (offset < nread && (rbuf[offset] == ' ' || rbuf[offset] == '\t' || rbuf[offset] == '\r'))
+ offset += 1;
+ if (offset == nread || rbuf[offset] != '{')
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID_REQUEST, "invalid request", NULL);
+ goto output_res;
+ }
+
+ const char *jsonrpc_q = nullptr;
+ int jsonrpc_q_len = 0;
+ char jsonrpc[8];
+ if (mjson_find (rbuf, (int) nread, "$.jsonrpc", &jsonrpc_q, &jsonrpc_q_len) != MJSON_TOK_STRING
+ || mjson_get_string (jsonrpc_q, jsonrpc_q_len, "$", jsonrpc, sizeof (jsonrpc)) != 3
+ || strcmp (jsonrpc, "2.0") != 0)
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID_REQUEST, "jsonrpc must be 2.0", NULL);
+ goto output_res;
+ }
+
+ int id_type = mjson_find (rbuf, (int) nread, "$.id", &r.id, &r.id_len);
+ has_id = id_type != MJSON_TOK_INVALID;
+ if (has_id && id_type != MJSON_TOK_STRING && id_type != MJSON_TOK_NUMBER)
+ {
+ r.id = nullptr;
+ r.id_len = 0;
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID_REQUEST, "id must be a string or number", NULL);
+ goto output_res;
+ }
+
+ const char8_t *methodname_q = nullptr;
+ int32_t methodname_q_len = 0;
+ if (mjson_find (rbuf, (int) nread, "$.method", (const char **) &methodname_q, &methodname_q_len) != MJSON_TOK_STRING)
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID_REQUEST, "method must be a string", NULL);
+ goto output_res;
+ }
+ if (methodname_q == nullptr || methodname_q_len == 0)
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID_REQUEST, "method must not be empty", NULL);
+ goto output_res;
+ }
+
+ methodname = mcpc_alloc ((size_t) methodname_q_len + 1);
+ if (methodname == nullptr)
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INTERNAL, "out of memory", NULL);
+ goto output_res;
+ }
+ methodname_len = mjson_get_string ((const char *) methodname_q, methodname_q_len, "$",
+ (char *) methodname, (size_t) methodname_q_len + 1);
+ if (methodname_len <= 0)
+ {
+ jsonrpc_return_error (&r, JSONRPC_ERROR_INVALID_REQUEST, "method is invalid", NULL);
+ goto output_res;
+ }
+ methodname[methodname_len] = 0;
+
+ mcpc_metho_t handle_what = MCPC_METHO_UNKNOWN; // nothing
+
+ if (false)
+ ;
+ else if (u8streq (methodname, u8c1 ("initialize")))
+ handle_what = MCPC_METHO_INITBEG;
+ else if (u8streq (methodname, u8c1 ("notifications/initialized")))
+ handle_what = MCPC_METHO_INITDONE;
+ else if (u8streq (methodname, u8c1 ("tools/list")))
+ handle_what = MCPC_METHO_LISTTOOL;
+ else if (u8streq (methodname, u8c1 ("tools/call")))
+ handle_what = MCPC_METHO_CALLTOOL;
+ else if (u8streq (methodname, u8c1 ("resources/list")))
+ handle_what = MCPC_METHO_LSRSC;
+ else if (u8streq (methodname, u8c1 ("prompts/list")))
+ handle_what = MCPC_METHO_PRMPT_LS;
+ else if (u8streq (methodname, u8c1 ("prompts/get")))
+ handle_what = MCPC_METHO_PRMPT_CALL;
+ else if (u8streq (methodname, u8c1 ("completion/complete")))
+ handle_what = MCPC_METHO_COMPLT_COMPLT;
+
+ if (handle_what == MCPC_METHO_UNKNOWN)
+ {
+ if (!has_id)
+ noreply = true;
+ else
+ jsonrpc_return_error (&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL);
+ goto output_res;
+ }
+
+ const char *params = nullptr;
+ int params_len = 0;
+ int params_type = mjson_find (rbuf, (int) nread, "$.params", &params, &params_len);
+ if (params_type != MJSON_TOK_INVALID && params_type != MJSON_TOK_OBJECT)
+ {
+ if (!has_id)
+ noreply = true;
+ else
+ jsonrpc_return_error (&r, JSONRPC_ERROR_BAD_PARAMS, "params must be an object", NULL);
+ goto output_res;
+ }
+ r.params = params_type == MJSON_TOK_OBJECT ? params : "";
+ r.params_len = params_type == MJSON_TOK_OBJECT ? params_len : 0;
+
+ if (is_metho_need_params (handle_what) && r.params_len < 1)
+ {
+ if (!has_id)
+ noreply = true;
+ else
+ jsonrpc_return_error (&r, JSONRPC_ERROR_BAD_PARAMS, "params are required", NULL);
+ goto output_res;
+ }
+
+ if (!has_id && handle_what != MCPC_METHO_INITDONE)
+ {
+ noreply = true;
+ goto output_res;
+ }
+
+ // Windows SOCKET is unsigned, while Unix file descriptors use a negative
+ // sentinel for stdio dispatch.
+#if is_win
+ if (!mcpc_sock_invalid (csock))
+#elif defined(is_unix)
+ if (csock > 0)
+#endif
+ { // TODO: more accurate?
+ mcpc_conn_t *conn0 = mcpc_conn_new (MCPC_CONN_FD, &csock);
+ mcpc_assert (MCPC_EC_0 == mcpc_connpool_addref ((mcpc_connpool_t *) sv->connpool, conn0), MCPC_EC_BUG);
+ }
+
+ if (false)
+ ;
+ else if (handle_what == MCPC_METHO_INITBEG)
+ _handle_initbeg (sv, &r, csock);
+ else if (handle_what == MCPC_METHO_INITDONE)
+ _handle_initdone (sv, &r, csock, &noreply);
+ else if (handle_what == MCPC_METHO_LISTTOOL)
+ _handle_tools_list (sv, &r, csock);
+ else if (handle_what == MCPC_METHO_LSRSC)
+ _handle_lsrsc (sv, &r, csock);
+ else if (handle_what == MCPC_METHO_CALLTOOL)
+ _handle_tools_call (sv, &r, csock);
+ else if (handle_what == MCPC_METHO_PRMPT_LS)
+ _handle_prmpt_list (sv, &r, csock);
+ else if (handle_what == MCPC_METHO_PRMPT_CALL)
+ _handle_prmpt_call (sv, &r, csock);
+ else if (handle_what == MCPC_METHO_COMPLT_COMPLT)
+ _handle_complt_complt (sv, &r, csock);
+
+output_res:
+ free (methodname);
+ if (noreply)
+ return;
+ if (false)
+ ;
+ else if (sv->typ == MCPC_SV_IOSTRM)
+ {
+ tuflog_d ("sv->rpcres:%.*s", sv->rpcres.len, sv->rpcres.ptr);
+ mcpc_conn_t *outconn = mcpc_connpool_getconn (sv->connpool, 1);
+ FILE *outstrm = mcpc_conn_getstrm (outconn);
+ int pres = fprintf (outstrm, "%.*s", sv->rpcres.len, sv->rpcres.ptr);
+ mcpc_assert (pres >= 0, MCPC_EC_BUG);
+ fflush (outstrm);
+ sv->rpcres.len = 0;
+ }
+ else if (sv->typ == MCPC_SV_TCP)
+ {
+ mcpc_assert (0 < send (csock, sv->rpcres.ptr, sv->rpcres.len, 0), MCPC_EC_BUG);
+ sv->rpcres.len = 0;
+ }
+}
diff --git a/third_party/mcpc/src/server.h b/third_party/mcpc/src/server.h
new file mode 100644
index 0000000..e2c4141
--- /dev/null
+++ b/third_party/mcpc/src/server.h
@@ -0,0 +1,196 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_server
+#define h_server
+
+#include <mcpc/visb.h>
+#include <mcpc/server.h>
+
+#include "ext_stdint.h"
+
+#include "errcode.h"
+#include "mjson.h"
+#include "rsc.h"
+#include "tool.h"
+#include "prmpt.h"
+
+#include <stdio.h>
+
+#if is_win
+#include <winsock2.h>
+typedef i32_t socklen_t;
+#endif
+
+#define BUFFER_SIZE 1024
+#define PORT 8080
+
+// ------------------------------ conn ------------------------------
+
+enum mcpc_conn_type
+{
+ MCPC_CONN_FD,
+ MCPC_CONN_STRM,
+};
+typedef enum mcpc_conn_type mcpc_conn_type_t;
+
+#if defined(is_unix)
+typedef int mcpc_sock_t;
+#elif is_win
+typedef SOCKET mcpc_sock_t;
+#endif
+
+static inline bool
+mcpc_sock_invalid (mcpc_sock_t sock)
+{
+#if defined(is_unix)
+ return sock < 0;
+#endif
+#if is_win
+ return sock == INVALID_SOCKET;
+#endif
+}
+
+enum mcpc_init_stat
+{
+ MCPC_INIT_NONE = 0,
+ MCPC_INIT_BEG,
+ MCPC_INIT_FAIL,
+ MCPC_INIT_SUCC,
+};
+typedef enum mcpc_init_stat mcpc_init_stat_t;
+
+typedef struct mcpc_conn mcpc_conn_t;
+struct mcpc_conn
+{
+ mcpc_conn_type_t typ;
+ union
+ {
+ mcpc_sock_t fd;
+ FILE *strm;
+ };
+ const ch8_t *cname;
+ u8_t cname_len;
+ mcpc_init_stat_t client_init;
+ mcpc_conn_t *nex;
+};
+
+typedef uint8_t mcpc_connpool_size_t;
+
+typedef struct mcpc_connpool mcpc_connpool_t;
+struct mcpc_connpool
+{
+ const mcpc_connpool_size_t cap;
+ mcpc_connpool_size_t len;
+ mcpc_conn_t *head;
+};
+
+// ----------------------------- server -----------------------------
+
+enum mcpc_server_type
+{
+ MCPC_SV_NONE = 0,
+ MCPC_SV_IOSTRM,
+ MCPC_SV_STDIO,
+ MCPC_SV_TCP,
+};
+typedef enum mcpc_server_type mcpc_server_type_t;
+
+struct mcpc_server
+{
+ mcpc_server_type_t typ;
+ const ch8_t *name;
+ u8_t name_len;
+ const ch8_t *ver;
+ u8_t ver_len;
+ u16_t capa;
+ const mcpc_toolpool_t *toolpool;
+ const mcpc_prmptpool_t *prmptpool;
+ const mcpc_rscpool_t *rscpool;
+ struct mjson_fixedbuf rpcres;
+ const mcpc_connpool_t *connpool;
+};
+// implication: for iostrm server, the first conn will be used as client init status storing
+// check mcpc_connpool_find_by_sock
+
+#define MCPC_CAPA_PRMT 0b0000000000000001
+#define MCPC_CAPA_PRMT_LISTCHG 0b0000000000000011
+#define MCPC_CAPA_TOOL 0b0000000000010000
+#define MCPC_CAPA_TOOL_LISTCHG 0b0000000000110000
+#define MCPC_CAPA_RSC 0b0000000100000000
+#define MCPC_CAPA_RSC_LISTCHG 0b0000001100000000
+#define MCPC_CAPA_RSC_SUBSRC 0b0000010100000000
+#define MCPC_CAPA_COMPLT 0b0001000000000000
+
+mcpc_server_t *mcpc_server_new (mcpc_server_type_t typ, mcpc_toolpool_t * toolpool, u32_t vargs_beg, ...);
+
+mcpc_server_t *mcpc_server_new_stdio ();
+
+/* void _handle_tools_list(mcpc_server_t *sv, struct jsonrpc_request *r); */
+
+/* void _handle_tools_call(mcpc_server_t *sv, struct jsonrpc_request *r); */
+
+mcpc_errcode_t mcpc_server_cleanup_rpcres (mcpc_server_t * sv);
+
+void handle_inone (mcpc_server_t * sv, const char *const rbuf, size_t nread, mcpc_sock_t csock);
+
+bool mcpc_server_supp_prompts (const mcpc_server_t * sv);
+
+bool mcpc_server_supp_prompts_lschg (const mcpc_server_t * sv);
+
+bool mcpc_server_supp_tools (const mcpc_server_t * sv);
+
+bool mcpc_server_supp_tools_lschg (const mcpc_server_t * sv);
+
+bool mcpc_server_supp_rscs (const mcpc_server_t * sv);
+
+bool mcpc_server_supp_rscs_lschg (const mcpc_server_t * sv);
+
+bool mcpc_server_supp_rscs_subs (const mcpc_server_t * sv);
+
+enum mcpc_metho
+{
+ MCPC_METHO_UNKNOWN = 0,
+ MCPC_METHO_INITBEG,
+ MCPC_METHO_INITDONE,
+ MCPC_METHO_LISTTOOL,
+ MCPC_METHO_CALLTOOL,
+ MCPC_METHO_LSRSC,
+ MCPC_METHO_PRMPT_LS,
+ MCPC_METHO_PRMPT_CALL,
+ MCPC_METHO_COMPLT_COMPLT,
+};
+typedef enum mcpc_metho mcpc_metho_t;
+
+// ---------------------------- connpool ----------------------------
+
+mcpc_conn_t *mcpc_conn_new (mcpc_conn_type_t typ, void *back);
+
+FILE *mcpc_conn_getstrm (mcpc_conn_t * conn);
+
+int mcpc_conn_getfd (mcpc_conn_t * conn);
+
+// TODO private
+mcpc_errcode_t mcpc_connpool_addref (mcpc_connpool_t * connpool, mcpc_conn_t * conn);
+
+mcpc_conn_t *mcpc_connpool_getconn (const mcpc_connpool_t * connpool, const mcpc_connpool_size_t idx);
+
+#endif
diff --git a/third_party/mcpc/src/tool.c b/third_party/mcpc/src/tool.c
new file mode 100644
index 0000000..7c746ec
--- /dev/null
+++ b/third_party/mcpc/src/tool.c
@@ -0,0 +1,505 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "tool.h"
+
+#include "ext_string.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include "errcode.h"
+#include "alloc.h"
+#include "anydata.h"
+
+
+/* = = = = = = = = = = = = = = = toolprop = = = = = = = = = = = = = = = */
+
+MCPC_API mcpc_toolprop_t *
+mcpc_toolprop_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len,
+ mcpc_anytype_t typ)
+{
+ mcpc_anydata_t *hp = mcpc_anydata_new_typ_meta1_meta2 (typ, name, name_len, desc, desc_len);
+
+ hp->flags |= ANYDATA_FL_REQUIRED;
+ hp->flags |= ANYDATA_FL_TOOLPROP;
+
+ return hp;
+}
+
+MCPC_API mcpc_toolprop_t *
+mcpc_toolprop_new2 (const char8_t *nament, const char8_t *descnt, mcpc_anytype_t typ)
+{
+ return mcpc_toolprop_new (nament, strlen ((const char *) nament), descnt, strlen ((const char *) descnt), typ);
+}
+
+MCPC_API bool
+mcpc_toolprop_is_required (const mcpc_toolprop_t *toolprop)
+{
+ return toolprop != nullptr && (toolprop->flags & ANYDATA_FL_REQUIRED) != 0;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_toolprop_set_required (mcpc_toolprop_t *toolprop, bool required)
+{
+ if (toolprop == nullptr)
+ return MCPC_EC_BUG;
+ if (required)
+ toolprop->flags |= ANYDATA_FL_REQUIRED;
+ else
+ toolprop->flags &= ~ANYDATA_FL_REQUIRED;
+ return MCPC_EC_0;
+}
+
+void
+mcpc_toolprop_free (mcpc_toolprop_t *toolprop)
+{
+ mcpc_anydata_free ((mcpc_anydata_t *) toolprop);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_getref_name (const mcpc_toolprop_t *toolprop, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta1 (toolprop, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_getref_desc (const mcpc_toolprop_t *toolprop, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta2 (toolprop, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_data_type (const mcpc_toolprop_t *toolprop, mcpc_anytype_t *ret)
+{
+ bug_if_nullptr (toolprop);
+ bug_if_nullptr (ret);
+ *ret = ((const mcpc_anydata_t *) toolprop)->data_typ;
+ return MCPC_EC_0;
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_nament (mcpc_toolprop_t *toolprop, const char8_t *name)
+{
+ return mcpc_anydata_set_meta1nt (toolprop, name);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i8 (mcpc_toolprop_t *toolprop, i8_t val)
+{
+ return mcpc_anydata_set_i8 (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u8 (mcpc_toolprop_t *toolprop, u8_t val)
+{
+ return mcpc_anydata_set_u8 (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i16 (mcpc_toolprop_t *toolprop, i16_t val)
+{
+ return mcpc_anydata_set_i16 (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u16 (mcpc_toolprop_t *toolprop, u16_t val)
+{
+ return mcpc_anydata_set_u16 (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i32 (mcpc_toolprop_t *toolprop, i32_t val)
+{
+ mcpc_errcode_t status = mcpc_anydata_set_i32 (toolprop, val);
+ if (status == MCPC_EC_0)
+ toolprop->flags |= ANYDATA_FL_PRESENT;
+ return status;
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u32 (mcpc_toolprop_t *toolprop, u32_t val)
+{
+ return mcpc_anydata_set_u32 (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i64 (mcpc_toolprop_t *toolprop, i64_t val)
+{
+ return mcpc_anydata_set_i64 (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u64 (mcpc_toolprop_t *toolprop, u64_t val)
+{
+ return mcpc_anydata_set_u64 (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_func (mcpc_toolprop_t *toolprop, void *val)
+{
+ return mcpc_anydata_set_func (toolprop, val);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i8 (const mcpc_toolprop_t *toolprop, i8_t *ret)
+{
+ return mcpc_anydata_get_i8 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u8 (const mcpc_toolprop_t *toolprop, u8_t *ret)
+{
+ return mcpc_anydata_get_u8 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i16 (const mcpc_toolprop_t *toolprop, i16_t *ret)
+{
+ return mcpc_anydata_get_i16 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u16 (const mcpc_toolprop_t *toolprop, u16_t *ret)
+{
+ return mcpc_anydata_get_u16 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i32 (const mcpc_toolprop_t *toolprop, i32_t *ret)
+{
+ return mcpc_anydata_get_i32 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u32 (const mcpc_toolprop_t *toolprop, u32_t *ret)
+{
+ return mcpc_anydata_get_u32 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i64 (const mcpc_toolprop_t *toolprop, i64_t *ret)
+{
+ return mcpc_anydata_get_i64 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u64 (const mcpc_toolprop_t *toolprop, u64_t *ret)
+{
+ return mcpc_anydata_get_u64 (toolprop, ret);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i8s (mcpc_toolprop_t *toolprop, const i8_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_i8s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u8s (mcpc_toolprop_t *toolprop, const u8_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_u8s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i16s (mcpc_toolprop_t *toolprop, const i16_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_i16s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u16s (mcpc_toolprop_t *toolprop, const u16_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_u16s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i32s (mcpc_toolprop_t *toolprop, const i32_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_i32s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u32s (mcpc_toolprop_t *toolprop, const u32_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_u32s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_i64s (mcpc_toolprop_t *toolprop, const i64_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_i64s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u64s (mcpc_toolprop_t *toolprop, const u64_t *arr, size_t arr_len)
+{
+ return mcpc_anydata_set_u64s (toolprop, arr, arr_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_set_u8str (mcpc_toolprop_t *toolprop, const char8_t *arr, size_t arr_len)
+{
+ mcpc_errcode_t status = mcpc_anydata_set_u8str (toolprop, arr, arr_len);
+ if (status == MCPC_EC_0)
+ toolprop->flags |= ANYDATA_FL_PRESENT;
+ return status;
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i8s (const mcpc_toolprop_t *toolprop, i8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_i8s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u8s (const mcpc_toolprop_t *toolprop, u8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_u8s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i16s (const mcpc_toolprop_t *toolprop, i16_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_i16s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u16s (const mcpc_toolprop_t *toolprop, u16_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_u16s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i32s (const mcpc_toolprop_t *toolprop, i32_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_i32s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u32s (const mcpc_toolprop_t *toolprop, u32_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_u32s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_i64s (const mcpc_toolprop_t *toolprop, i64_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_i64s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u64s (const mcpc_toolprop_t *toolprop, u64_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_u64s (toolprop, ret, ret_cap, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_toolprop_get_u8str (const mcpc_toolprop_t *toolprop, char8_t *ret, size_t ret_cap, size_t *ret_len)
+{
+ return mcpc_anydata_get_u8str (toolprop, ret, ret_cap, ret_len);
+}
+
+/* = = = = = = = = = = = = = toolproppool = = = = = = = = = = = = = */
+
+size_t
+mcpc_toolproppool_get_len (const mcpc_toolproppool_t *toolproppool)
+{
+ bug_if_nullptr (toolproppool);
+ size_t ret = 0;
+ ret = ((const mcpc_anypool_t *) toolproppool)->len;
+ return ret;
+}
+
+const mcpc_toolprop_t *
+mcpc_toolproppool_get (const mcpc_toolproppool_t *toolproppool, size_t idx)
+{
+ bug_if_nullptr (toolproppool);
+ return ((mcpc_anypool_t *) toolproppool)->head + idx;
+}
+
+/* = = = = = = = = = = = = = = = = = tool = = = = = = = = = = = = = = = = = */
+
+MCPC_API mcpc_tool_t *
+mcpc_tool_new (const char8_t *name, const size_t name_len, const char8_t *desc, const size_t desc_len)
+{
+ mcpc_anydata_t *hp = mcpc_anydata_new_meta1_meta2 (name, name_len, desc, desc_len);
+
+ hp->flags |= ANYDATA_FL_TOOL;
+
+ return hp;
+}
+
+MCPC_API mcpc_tool_t *
+mcpc_tool_new2 (const char8_t *nament, const char8_t *descnt)
+{
+ return mcpc_tool_new (nament, strlen ((const char *) nament), descnt, strlen ((const char *) descnt));
+}
+
+void
+mcpc_tool_free (mcpc_tool_t *tool)
+{
+ mcpc_anydata_free ((mcpc_anydata_t *) tool);
+}
+
+mcpc_errcode_t
+mcpc_tool_getref_name (const mcpc_tool_t *tool, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta1 (tool, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_tool_getref_desc (const mcpc_tool_t *tool, const char8_t **ret, size_t *ret_len)
+{
+ return mcpc_anydata_getref_meta2 (tool, ret, ret_len);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_get_call_cb (const mcpc_tool_t *tool, mcpc_tcallcb_t *ret)
+{
+ return mcpc_anydata_get_func (tool, ret);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_set_call_cb (mcpc_tool_t *tool, mcpc_tcallcb_t cb)
+{
+ mcpc_anydata_set_func (tool, cb);
+ return MCPC_EC_0;
+}
+
+MCPC_API void
+mcpc_tool_addfre_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop)
+{
+ mcpc_anydata_add_child (tool, toolprop); // TODO return EC
+}
+
+MCPC_API void
+mcpc_tool_addcpy_toolprop (mcpc_tool_t *tool, mcpc_toolprop_t *toolprop)
+{
+ mcpc_anydata_addcpy_child (tool, toolprop); // TODO return EC
+}
+
+MCPC_API const mcpc_toolprop_t *
+mcpc_tool_get_toolprop (const mcpc_tool_t *tool, size_t idx)
+{
+ return mcpc_anydata_get_child (tool, idx);
+}
+
+size_t
+mcpc_tool_get_toolprop_len (const mcpc_tool_t *tool)
+{
+ bug_if_nullptr (tool);
+ mcpc_anydata_t *tool_imp = (mcpc_anydata_t *) tool;
+ if (tool_imp->chd_pool == nullptr)
+ return 0;
+ return tool_imp->chd_pool->len;
+}
+
+const mcpc_toolprop_t *
+mcpc_tool_getbynament_toolprop (const mcpc_tool_t *tool, const char8_t *tprop_nament)
+{
+ const mcpc_anydata_t *chd = mcpc_anypool_getbymeta1nt (tool->chd_pool, tprop_nament);
+ return (mcpc_toolprop_t *) chd;
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_get_tpropval_i32 (const mcpc_tool_t *tool, const char8_t *tprop_nament, i32_t *ret)
+{
+ bug_if_nullptr (tool);
+ bug_if_nullptr (tprop_nament);
+ bug_if_nullptr (ret);
+ const mcpc_toolprop_t *chd = mcpc_tool_getbynament_toolprop (tool, tprop_nament);
+ if (chd == nullptr)
+ return MCPC_EC_TOOLPROP_NOTFOUND;
+ if ((chd->flags & ANYDATA_FL_PRESENT) == 0)
+ return MCPC_EC_ANYDATA_NOTFOUND;
+
+ return mcpc_toolprop_get_i32 (chd, ret);
+}
+
+MCPC_API mcpc_errcode_t
+mcpc_tool_get_tpropval_u8str (const mcpc_tool_t *tool, const char8_t *tprop_nament, char8_t *ret,
+ size_t ret_cap, size_t *ret_len)
+{
+ bug_if_nullptr (tool);
+ bug_if_nullptr (tprop_nament);
+ bug_if_nullptr (ret);
+ const mcpc_toolprop_t *chd = mcpc_tool_getbynament_toolprop (tool, tprop_nament);
+ if (chd == nullptr)
+ return MCPC_EC_TOOLPROP_NOTFOUND;
+ if ((chd->flags & ANYDATA_FL_PRESENT) == 0)
+ return MCPC_EC_ANYDATA_NOTFOUND;
+
+ return mcpc_anydata_get_u8str (chd, ret, ret_cap, ret_len);
+}
+
+const mcpc_toolproppool_t *
+mcpc_tool_get_toolproppool (const mcpc_tool_t *tool)
+{
+ return tool->chd_pool;
+}
+
+/* = = = = = = = = = = = = = = = toolpool = = = = = = = = = = = = = = = */
+
+mcpc_toolpool_t *
+mcpc_toolpool_new ()
+{
+ return mcpc_anypool_new ();
+}
+
+void
+mcpc_toolpool_free (mcpc_toolpool_t *toolpool)
+{
+ mcpc_anypool_free (toolpool);
+}
+
+void
+mcpc_toolpool_addcpy (mcpc_toolpool_t *toolpool, const mcpc_tool_t *tool)
+{
+ mcpc_anypool_addcpy (toolpool, tool);
+}
+
+void
+mcpc_toolpool_addfre (mcpc_toolpool_t *toolpool, mcpc_tool_t *tool)
+{
+ mcpc_anypool_addfre (toolpool, tool);
+}
+
+size_t
+mcpc_toolpool_get_len (const mcpc_toolpool_t *toolpool)
+{
+ bug_if_nullptr (toolpool);
+ size_t ret = 0;
+ ret = ((const mcpc_anypool_t *) toolpool)->len;
+ return ret;
+}
+
+const mcpc_tool_t *
+mcpc_toolpool_get (const mcpc_toolpool_t *toolpool, size_t idx)
+{
+ return mcpc_anypool_get (toolpool, idx);
+}
+
+const mcpc_tool_t *
+mcpc_toolpool_getbyname (const mcpc_toolpool_t *toolpool, const char8_t *name, size_t name_len)
+{
+ return mcpc_anypool_getbymeta1 (toolpool, name, name_len);
+}
diff --git a/third_party/mcpc/src/tool.h b/third_party/mcpc/src/tool.h
new file mode 100644
index 0000000..7da9bcd
--- /dev/null
+++ b/third_party/mcpc/src/tool.h
@@ -0,0 +1,150 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_tool
+#define h_tool
+
+#include <mcpc/tool.h>
+
+#include "ext_stdint.h"
+
+typedef mcpc_anypool_t mcpc_toolpool_t;
+typedef mcpc_anypool_t mcpc_toolproppool_t;
+
+// ---------------------------- toolprop ----------------------------
+
+void mcpc_toolprop_free (mcpc_toolprop_t * toolprop);
+
+mcpc_errcode_t mcpc_toolprop_getref_name (const mcpc_toolprop_t * toolprop, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_getref_desc (const mcpc_toolprop_t * toolprop, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_data_type (const mcpc_toolprop_t * toolprop, mcpc_anytype_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_set_nament (mcpc_toolprop_t * toolprop, const char8_t * name);
+
+mcpc_errcode_t mcpc_toolprop_set_i8 (mcpc_toolprop_t * toolprop, i8_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_u8 (mcpc_toolprop_t * toolprop, u8_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_i16 (mcpc_toolprop_t * toolprop, i16_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_u16 (mcpc_toolprop_t * toolprop, u16_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_i32 (mcpc_toolprop_t * toolprop, i32_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_u32 (mcpc_toolprop_t * toolprop, u32_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_i64 (mcpc_toolprop_t * toolprop, i64_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_u64 (mcpc_toolprop_t * toolprop, u64_t val);
+
+mcpc_errcode_t mcpc_toolprop_set_func (mcpc_toolprop_t * toolprop, void *val);
+
+mcpc_errcode_t mcpc_toolprop_get_i8 (const mcpc_toolprop_t * toolprop, i8_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_u8 (const mcpc_toolprop_t * toolprop, u8_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_i16 (const mcpc_toolprop_t * toolprop, i16_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_u16 (const mcpc_toolprop_t * toolprop, u16_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_i32 (const mcpc_toolprop_t * toolprop, i32_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_u32 (const mcpc_toolprop_t * toolprop, u32_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_i64 (const mcpc_toolprop_t * toolprop, i64_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_u64 (const mcpc_toolprop_t * toolprop, u64_t * ret);
+
+mcpc_errcode_t mcpc_toolprop_get_func (const mcpc_toolprop_t * toolprop, void *ret);
+
+mcpc_errcode_t mcpc_toolprop_set_i8s (mcpc_toolprop_t * toolprop, const i8_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_u8s (mcpc_toolprop_t * toolprop, const u8_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_i16s (mcpc_toolprop_t * toolprop, const i16_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_u16s (mcpc_toolprop_t * toolprop, const u16_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_i32s (mcpc_toolprop_t * toolprop, const i32_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_u32s (mcpc_toolprop_t * toolprop, const u32_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_i64s (mcpc_toolprop_t * toolprop, const i64_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_u64s (mcpc_toolprop_t * toolprop, const u64_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_set_u8str (mcpc_toolprop_t * toolprop, const char8_t * arr, size_t arr_len);
+
+mcpc_errcode_t mcpc_toolprop_get_i8s (const mcpc_toolprop_t * toolprop, i8_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_u8s (const mcpc_toolprop_t * toolprop, u8_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_i16s (const mcpc_toolprop_t * toolprop, i16_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_u16s (const mcpc_toolprop_t * toolprop, u16_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_i32s (const mcpc_toolprop_t * toolprop, i32_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_u32s (const mcpc_toolprop_t * toolprop, u32_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_i64s (const mcpc_toolprop_t * toolprop, i64_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_u64s (const mcpc_toolprop_t * toolprop, u64_t * ret, size_t ret_cap, size_t *ret_len);
+
+mcpc_errcode_t mcpc_toolprop_get_u8str (const mcpc_toolprop_t * toolprop, char8_t * ret, size_t ret_cap,
+ size_t *ret_len);
+
+const mcpc_toolproppool_t *mcpc_tool_get_toolproppool (const mcpc_tool_t * tool);
+
+// -------------------------- toolproppool --------------------------
+
+size_t mcpc_toolproppool_get_len (const mcpc_toolproppool_t * toolproppool);
+
+const mcpc_toolprop_t *mcpc_toolproppool_get (const mcpc_toolproppool_t * toolproppool, size_t idx);
+
+// ------------------------------ tool ------------------------------
+
+mcpc_errcode_t mcpc_tool_getref_name (const mcpc_tool_t * tool, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_tool_getref_desc (const mcpc_tool_t * tool, const char8_t ** ret, size_t *ret_len);
+
+void mcpc_tool_free (mcpc_tool_t * tool);
+
+// ---------------------------- toolpool ----------------------------
+
+mcpc_toolpool_t *mcpc_toolpool_new ();
+
+void mcpc_toolpool_free (mcpc_toolpool_t * toolpool);
+
+void mcpc_toolpool_addcpy (mcpc_toolpool_t * toolpool, const mcpc_tool_t * tool);
+
+void mcpc_toolpool_addfre (mcpc_toolpool_t * toolpool, mcpc_tool_t * tool);
+
+size_t mcpc_toolpool_get_len (const mcpc_toolpool_t * toolpool);
+
+const mcpc_tool_t *mcpc_toolpool_get (const mcpc_toolpool_t * toolpool, size_t idx);
+
+const mcpc_tool_t *mcpc_toolpool_getbyname (const mcpc_toolpool_t * toolpool, const char8_t * name, size_t name_len);
+
+
+#endif
diff --git a/third_party/mcpc/src/ucbr.c b/third_party/mcpc/src/ucbr.c
new file mode 100644
index 0000000..5dfce26
--- /dev/null
+++ b/third_party/mcpc/src/ucbr.c
@@ -0,0 +1,288 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#include "ucbr.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "alloc.h"
+#include "anydata.h"
+#include "errcode.h"
+#include "ext_string.h"
+
+/* = = = = = = = = = = = = = = = = ucbrchd = = = = = = = = = = = = = = = = */
+
+mcpc_ucbrchd_t *
+mcpc_ucbrchd_new_vprintf8 (const char8_t *fmt, va_list *vargs)
+{
+ size_t sbuf_cap = 1024;
+ size_t sbuf_len = 0;
+ char8_t *sbuf = mcpc_alloc (sbuf_cap);
+ int printed = 0;
+ while (true)
+ {
+ va_list vargs_cp;
+ va_copy (vargs_cp, *vargs);
+ printed
+ = vsnprintf ((char *)sbuf, sbuf_cap, (const char *)fmt, vargs_cp);
+ bug_if (printed < 0);
+ if ((size_t)printed >= sbuf_cap)
+ {
+ sbuf_cap += sbuf_cap;
+ sbuf = mcpc_realloc (sbuf, sbuf_cap);
+ }
+ else
+ {
+ sbuf_len = (size_t)printed;
+ break;
+ }
+ }
+
+ mcpc_anydata_t *anydata = mcpc_anydata_new_typ (MCPC_U8STR);
+
+ if (MCPC_EC_0 != mcpc_anydata_set_u8str (anydata, sbuf, sbuf_len))
+ goto badret;
+
+ free (sbuf);
+ return (mcpc_ucbrchd_t *)anydata;
+
+badret:
+ free (sbuf);
+ mcpc_anydata_free (anydata);
+ return nullptr;
+}
+
+mcpc_errcode_t
+mcpc_ucbrchd_getref_u8str (const mcpc_ucbrchd_t *ucbrchd, const char8_t **ret,
+ size_t *ret_len)
+{
+ return mcpc_anydata_getref_u8str (ucbrchd, ret, ret_len);
+}
+
+mcpc_errcode_t
+mcpc_ucbrchd_get_data_type (const mcpc_ucbrchd_t *ucbrchd, mcpc_anytype_t *ret)
+{
+ bug_if_nullptr (ucbrchd);
+ bug_if_nullptr (ret);
+
+ *ret = ucbrchd->data_typ;
+
+ return MCPC_EC_0;
+}
+
+bool
+mcpc_ucbrchd_is_tool_callres (const mcpc_ucbrchd_t *ucbrchd)
+{
+ return ucbrchd->flags & ANYDATA_FL_USRCBRES_TOOLCALL;
+}
+
+bool
+mcpc_ucbrchd_is_prmpt_callres (const mcpc_ucbrchd_t *ucbrchd)
+{
+ return ucbrchd->flags & ANYDATA_FL_USRCBRES_PRMPTCALL;
+}
+
+bool
+mcpc_ucbrchd_is_assist_prmpt (const mcpc_ucbrchd_t *ucbrchd)
+{
+ return ucbrchd->flags & ANYDATA_FL_USRCBRES_PRMPTCALL_ASSIST;
+}
+
+/* = = = = = = = = = = = = = = = = = ucbr = = = = = = = = = = = = = = = = = */
+
+mcpc_ucbr_t *
+mcpc_ucbr_new ()
+{
+ return mcpc_anypool_new ();
+}
+
+void
+mcpc_ucbr_free (mcpc_ucbr_t *ucbr)
+{
+ mcpc_anypool_free (ucbr);
+}
+
+void
+mcpc_ucbr_addcpy (mcpc_ucbr_t *ucbr, const mcpc_ucbrchd_t *ucbrchd)
+{
+ mcpc_anypool_addcpy (ucbr, ucbrchd);
+}
+
+void
+mcpc_ucbr_addfre (mcpc_ucbr_t *ucbr, mcpc_ucbrchd_t *ucbrchd)
+{
+ mcpc_anypool_addfre (ucbr, ucbrchd);
+}
+
+bool
+mcpc_ucbr_is_err (const mcpc_ucbr_t *ucbr)
+{
+ bug_if_nullptr (ucbr);
+ return ucbr->flags & ANYPOOL_FL_USRCBRES_TOOLCALL_ERROR;
+}
+
+size_t
+mcpc_ucbr_get_len (const mcpc_ucbr_t *ucbr)
+{
+ bug_if_nullptr (ucbr);
+ size_t ret = 0;
+ ret = ((const mcpc_anypool_t *)ucbr)->len;
+ return ret;
+}
+
+const mcpc_ucbrchd_t *
+mcpc_ucbr_get (const mcpc_ucbr_t *ucbr, size_t idx)
+{
+ return mcpc_anypool_get (ucbr, idx);
+}
+
+const mcpc_ucbrchd_t *
+mcpc_ucbr_getbyname (const mcpc_ucbr_t *ucbr, const char8_t *name,
+ size_t name_len)
+{
+ return mcpc_anypool_getbymeta1 (ucbr, name, name_len);
+}
+
+const mcpc_ucbrchd_t *
+mcpc_ucbr_get_next (const mcpc_ucbr_t *ucbr)
+{
+ bug_if_nullptr (ucbr);
+
+ static size_t idx = 0;
+
+ const mcpc_anypool_t *pool = ucbr;
+ if (pool == nullptr)
+ return nullptr;
+
+ const size_t pool_len = pool->len;
+ if (pool_len == 0)
+ return nullptr;
+
+ if (idx == pool_len) // TODO MT-SAFE
+ {
+ idx = 0;
+ return nullptr;
+ }
+
+ const mcpc_anydata_t *ret = pool->head + idx;
+ idx += 1;
+
+ return ret;
+}
+
+size_t
+mcpc_ucbr_get_serlz_lenhint (const mcpc_ucbr_t *ucbr)
+{
+ bug_if_nullptr (ucbr);
+ size_t ret = 0;
+
+ size_t length = mcpc_ucbr_get_len (ucbr);
+ for (size_t i = 0; i < length; ++i)
+ {
+ const mcpc_ucbrchd_t *cur_chd = mcpc_ucbr_get (ucbr, i);
+ ret += cur_chd->data_len;
+ }
+
+ /* JSON string escaping can expand one input byte to six output bytes. */
+ if (ret > (SIZE_MAX - 1024) / 6)
+ return 0;
+ ret = ret * 6 + 1024;
+
+ return ret;
+}
+
+void
+mcpc_ucbr_toolcallres_add_text_vprintf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt,
+ va_list *vargs)
+{
+ bug_if_nullptr (ucbr);
+ bug_if_nullptr (fmt);
+ bug_if_nullptr (vargs);
+
+ ucbr->flags |= ANYPOOL_FL_USRCBRES_TOOLCALL;
+
+ mcpc_ucbrchd_t *newctn = mcpc_ucbrchd_new_vprintf8 (fmt, vargs);
+ newctn->flags |= ANYDATA_FL_USRCBRES_TOOLCALL;
+ newctn->flags |= ANYDATA_FL_USRCBRES_TOOLCALL_TXTCTN;
+ mcpc_ucbr_addfre (ucbr, newctn);
+}
+
+MCPC_API void
+mcpc_ucbr_toolcall_add_text_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt,
+ ...)
+{
+ va_list vargs;
+ va_start (vargs, fmt);
+
+ mcpc_ucbr_toolcallres_add_text_vprintf8 (ucbr, fmt, &vargs);
+
+ va_end (vargs);
+}
+
+MCPC_API void
+mcpc_ucbr_toolcall_add_errmsg_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt,
+ ...)
+{
+ va_list vargs;
+ va_start (vargs, fmt);
+
+ ucbr->flags |= ANYPOOL_FL_USRCBRES_TOOLCALL_ERROR;
+ mcpc_ucbr_toolcallres_add_text_vprintf8 (ucbr, fmt, &vargs);
+
+ va_end (vargs);
+}
+
+MCPC_API void
+mcpc_ucbr_prmptget_add_user_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt,
+ ...)
+{
+ va_list vargs;
+ va_start (vargs, fmt);
+
+ ucbr->flags |= ANYPOOL_FL_USRCBRES_PRMPTCALL;
+
+ mcpc_ucbrchd_t *newctn = mcpc_ucbrchd_new_vprintf8 (fmt, &vargs);
+ newctn->flags |= ANYDATA_FL_USRCBRES_PRMPTCALL;
+
+ mcpc_ucbr_addfre (ucbr, newctn);
+
+ va_end (vargs);
+}
+
+MCPC_API void
+mcpc_ucbr_prmptget_add_assist_printf8 (mcpc_ucbr_t *ucbr, const char8_t *fmt,
+ ...)
+{
+ va_list vargs;
+ va_start (vargs, fmt);
+
+ ucbr->flags |= ANYPOOL_FL_USRCBRES_PRMPTCALL;
+
+ mcpc_ucbrchd_t *newctn = mcpc_ucbrchd_new_vprintf8 (fmt, &vargs);
+ newctn->flags |= ANYDATA_FL_USRCBRES_PRMPTCALL;
+ newctn->flags |= ANYDATA_FL_USRCBRES_PRMPTCALL_ASSIST;
+
+ mcpc_ucbr_addfre (ucbr, newctn);
+
+ va_end (vargs);
+}
diff --git a/third_party/mcpc/src/ucbr.h b/third_party/mcpc/src/ucbr.h
new file mode 100644
index 0000000..796688d
--- /dev/null
+++ b/third_party/mcpc/src/ucbr.h
@@ -0,0 +1,61 @@
+// This file is part of the mcpc library.
+//
+// Copyright (C) 2025 Michael Lee <micl2e2 AT proton.me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#ifndef h_ucbr
+#define h_ucbr
+
+#include <stddef.h>
+
+#include <mcpc/ucbr.h>
+
+typedef mcpc_anydata_t mcpc_ucbrchd_t;
+
+bool mcpc_ucbrchd_is_tool_callres (const mcpc_ucbrchd_t * ucbrchd);
+
+bool mcpc_ucbrchd_is_prmpt_callres (const mcpc_ucbrchd_t * ucbrchd);
+
+bool mcpc_ucbrchd_is_assist_prmpt (const mcpc_ucbrchd_t * ucbrchd);
+
+mcpc_errcode_t mcpc_ucbrchd_getref_u8str (const mcpc_ucbrchd_t * ucbrchd, const char8_t ** ret, size_t *ret_len);
+
+mcpc_errcode_t mcpc_ucbrchd_get_data_type (const mcpc_ucbrchd_t * ucbrchd, mcpc_anytype_t * ret);
+
+// ------------------------------ ucbr ------------------------------
+
+mcpc_ucbr_t *mcpc_ucbr_new ();
+
+void mcpc_ucbr_free (mcpc_ucbr_t * ucbr);
+
+size_t mcpc_ucbr_get_len (const mcpc_ucbr_t * ucbr);
+
+const mcpc_ucbrchd_t *mcpc_ucbr_get (const mcpc_ucbr_t * ucbr, size_t idx);
+
+const mcpc_ucbrchd_t *mcpc_ucbr_get_next (const mcpc_ucbr_t * ucbr);
+
+size_t mcpc_ucbr_get_serlz_lenhint (const mcpc_ucbr_t * ucbr);
+
+void mcpc_ucbr_addfre (mcpc_ucbr_t * ucbr, mcpc_ucbrchd_t * ucbrchd);
+
+bool mcpc_ucbr_is_err (const mcpc_ucbr_t * ucbr);
+
+
+
+#endif
diff --git a/third_party/mcpc/src/ver.txt b/third_party/mcpc/src/ver.txt
new file mode 100644
index 0000000..6c6aa7c
--- /dev/null
+++ b/third_party/mcpc/src/ver.txt
@@ -0,0 +1 @@
+0.1.0 \ No newline at end of file
diff --git a/third_party/mcpc/tst/.indent.pro b/third_party/mcpc/tst/.indent.pro
new file mode 100644
index 0000000..f7bb493
--- /dev/null
+++ b/third_party/mcpc/tst/.indent.pro
@@ -0,0 +1 @@
+../.indent.pro \ No newline at end of file
diff --git a/third_party/mcpc/tst/CMakeLists.txt b/third_party/mcpc/tst/CMakeLists.txt
new file mode 100644
index 0000000..d9b5bd5
--- /dev/null
+++ b/third_party/mcpc/tst/CMakeLists.txt
@@ -0,0 +1,33 @@
+
+
+add_compile_options(/Dis_win)
+add_compile_options(/Dis_wincl)
+add_compile_options(/nologo)
+add_compile_options(/std:c17)
+add_compile_options(/DMCPC_C23PTCH_KW1)
+add_compile_options(/DMCPC_C23PTCH_CKD1)
+add_compile_options(/DMCPC_C23PTCH_UCHAR1)
+
+
+# --------------------------- Add A Test ---------------------------
+
+set (CurrTestName server1)
+
+add_executable (${CurrTestName} ${CurrTestName}.c commtst.c)
+
+target_include_directories (
+ ${CurrTestName}
+ PRIVATE ${PROJECT_SOURCE_DIR}
+ PRIVATE ${PROJECT_SOURCE_DIR}/src
+)
+
+target_link_libraries (${CurrTestName} ws2_32)
+target_link_libraries (${CurrTestName} mcpc)
+
+add_test(NAME ${CurrTestName} COMMAND ${CurrTestName})
+
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/server1-req-initialize-1.json
+ ${CMAKE_CURRENT_BINARY_DIR}/server1-req-initialize-1.json
+ COPYONLY
+)
diff --git a/third_party/mcpc/tst/Makefile b/third_party/mcpc/tst/Makefile
new file mode 100644
index 0000000..5951716
--- /dev/null
+++ b/third_party/mcpc/tst/Makefile
@@ -0,0 +1,108 @@
+include ../mk/step0.mk
+
+ifneq (,$(is_linux))
+ include ../mk/ccdefs-linux.mk
+endif
+ifneq (,$(is_cygwin))
+ include ../mk/ccdefs-cygwin.mk
+endif
+ifneq (,$(is_mingw))
+ include ../mk/ccdefs-mingw.mk
+endif
+ifneq (,$(is_macos))
+ include ../mk/ccdefs-macos.mk
+endif
+ifneq (,$(is_win))
+ include ../mk/ccdefs-win.mk
+endif
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+include ../mk/dbgrel.mk
+
+
+ifneq (,$(is_gcclike))
+ CFLAGS += -I../src
+endif
+ifneq (,$(is_cllike))
+ CFLAGS += /I..\src
+endif
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+default: link utst
+
+clean:
+ $(if $(is_cmd) , del *.pdb *.ilk *.o *.out *.a *.so *.dSYM *.obj *.exe *.lib *.dll *.exp *.log )
+ $(if $(is_sh) , rm -rf *.pdb *.ilk *.o *.out *.a *.so *.dSYM *.obj *.exe *.lib *.dll *.exp *.log )
+
+comp: itst-svcalc_COMP
+
+link: itst-svcalc_LINKE
+
+utst: itst-svcalc_EXEC
+
+itst: itst-svcalc_EXEC
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# itst-svcalc
+
+itst-svcalc_srcs =
+itst-svcalc_srcs += commtst.c
+itst-svcalc_srcs += itst-svcalc.c
+
+itst-svcalc_objs =
+ifneq (,$(is_gcclike))
+ itst-svcalc_objs = $(patsubst %.c,%.o,$(itst-svcalc_srcs))
+else ifneq (,$(is_cllike))
+ itst-svcalc_objs = $(patsubst %.c,%.obj,$(itst-svcalc_srcs))
+endif
+
+itst-svcalc_ldflags =
+ifneq (,$(is_unixar))
+ itst-svcalc_ldflags += ../src/libmcpc.a
+endif
+ifneq (,$(is_manual_pthread))
+ itst-svcalc_ldflags += -lpthread
+endif
+ifneq (,$(is_winlib))
+ itst-svcalc_ldflags += ws2_32.lib
+ itst-svcalc_ldflags += ..\src\mcpc.lib
+endif
+ifneq (,$(is_winld))
+ itst-svcalc_ldflags += -lws2_32
+endif
+
+
+itst-svcalc_COMP:
+ $(if $(is_gcclike) , $(CC) $(CFLAGS) $(itst-svcalc_srcs) -c )
+ $(if $(is_cllike) , $(CC) $(CFLAGS) $(itst-svcalc_srcs) /c )
+itst-svcalc_LINKE: itst-svcalc_COMP
+ $(if $(is_gcclike) , $(CC) $(itst-svcalc_objs) $(LDFLAGS) $(itst-svcalc_ldflags) -o itst-svcalc.out )
+ $(if $(is_cllike) , $(CC) $(itst-svcalc_objs) /link /out:itst-svcalc.exe $(LDFLAGS) $(itst-svcalc_ldflags))
+itst-svcalc_EXEC: itst-svcalc_LINKE
+ @ echo>>$@.stdout.log; echo>>$@.stderr.log;
+ $(if $(is_unix) , @ date>>$@.stdout.log; date>>$@.stderr.log; )
+ $(if $(is_cmd) , echo ___>>$@.stdout.log; echo ___>>$@.stderr.log; )
+ $(if $(is_pkill) , pkill itst-sv || printf "" )
+ # itst-svcalc toolslist
+ $(if $(is_dirsh) , ./itst-svcalc.out iostrm -i itst-svcalc-req-toolslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(is_cmd) , itst-svcalc.exe iostrm -i itst-svcalc-req-toolslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(and $(is_indirsh),$(is_cllike)) , ./itst-svcalc.exe iostrm -i itst-svcalc-req-toolslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(and $(is_indirsh),$(is_gcclike)) , ./itst-svcalc.out iostrm -i itst-svcalc-req-toolslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(is_valg) , valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=222 ./itst-svcalc.out iostrm -i itst-svcalc-req-toolslist-1.json 1>/dev/null 2>/dev/null )
+ # itst-svcalc promptslist
+ $(if $(is_dirsh) , ./itst-svcalc.out iostrm -i itst-svcalc-req-promptslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(is_cmd) , itst-svcalc.exe iostrm -i itst-svcalc-req-promptslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(and $(is_indirsh),$(is_cllike)) , ./itst-svcalc.exe iostrm -i itst-svcalc-req-promptslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(and $(is_indirsh),$(is_gcclike)) , ./itst-svcalc.out iostrm -i itst-svcalc-req-promptslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(is_valg) , valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=222 ./itst-svcalc.out iostrm -i itst-svcalc-req-promptslist-1.json 1>/dev/null 2>/dev/null )
+ # itst-svcalc resourceslist
+ $(if $(is_dirsh) , ./itst-svcalc.out iostrm -i itst-svcalc-req-resourceslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(is_cmd) , itst-svcalc.exe iostrm -i itst-svcalc-req-resourceslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(and $(is_indirsh),$(is_cllike)) , ./itst-svcalc.exe iostrm -i itst-svcalc-req-resourceslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(and $(is_indirsh),$(is_gcclike)) , ./itst-svcalc.out iostrm -i itst-svcalc-req-resourceslist-1.json 1>>$@.stdout.log 2>>$@.stderr.log )
+ $(if $(is_valg) , valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=222 ./itst-svcalc.out iostrm -i itst-svcalc-req-resourceslist-1.json 1>/dev/null 2>/dev/null )
+ @echo P A S S E D : $(patsubst %_EXEC,%,$@)
diff --git a/third_party/mcpc/tst/commtst.c b/third_party/mcpc/tst/commtst.c
new file mode 100644
index 0000000..a2ba8dc
--- /dev/null
+++ b/third_party/mcpc/tst/commtst.c
@@ -0,0 +1,181 @@
+#include "commtst.h"
+
+#include <stdint.h>
+
+i32_t
+fsize (FILE *strm)
+{
+ fseek (strm, 0, SEEK_SET);
+ fseek (strm, 0, SEEK_END);
+ long sz = ftell (strm);
+ assert (sz > 0);
+ fseek (strm, 0, SEEK_SET);
+ return sz;
+}
+
+void
+read_atmost (void *src, char *rbuf, const size_t nmost, size_t *nread)
+{
+#ifdef is_win
+ HANDLE *hdlp = (HANDLE *) src;
+#else
+ int *fdp = (int *) src;
+#endif
+ size_t nread_accm = 0;
+ size_t nleft = nmost;
+ while (nleft > 0)
+ {
+ ssize_t nbuffered = nmost - nleft;
+#ifdef is_win
+ ssize_t nread_cur = 0;
+ assT (ReadFile
+ (*hdlp, rbuf + nbuffered, (DWORD)nleft, (LPDWORD) (&nread_cur),
+ nullptr));
+#else
+ ssize_t nread_cur = read (*fdp, rbuf + nbuffered, nleft);
+#endif
+ if (nread_cur <= 0)
+ break;
+ nread_accm += nread_cur;
+ nleft -= nread_cur;
+ }
+ *nread = nread_accm;
+}
+
+void
+recv_atmost (
+#if is_unix
+ int csock,
+#elif is_win
+ SOCKET csock,
+#endif
+ char *rbuf, const size_t nmost, size_t *nread)
+{
+ size_t nread_accm = 0;
+ size_t nleft = nmost;
+ while (nleft > 0)
+ {
+ ssize_t nbuffered = nmost - nleft;
+ ssize_t nread_cur = recv (csock, rbuf + nbuffered, (int)nleft, 0);
+ if (nread_cur <= 0)
+ break;
+ nread_accm += nread_cur;
+ nleft -= nread_cur;
+ }
+ *nread = nread_accm;
+}
+
+i32_t
+read_u8str (const char8_t *const filename, char8_t *const destbuf)
+{
+ FILE *file;
+
+#if is_win
+ fopen_s (&file, (const char*) filename, "rb,ccs=UTF-8");
+#else
+ file = fopen ((const char*) filename, "rb");
+#endif
+ assert (file != nullptr);
+
+ i32_t fsz = fsize (file);
+
+ size_t bytes_read = fread (destbuf, 1, fsz, file);
+ assert (bytes_read > 0);
+
+ // null-terminate the buffer
+ destbuf[bytes_read] = 0;
+ // printf("File content: %s\n", destbuf);
+
+ fclose (file);
+
+ return 0;
+}
+
+i32_t
+read_u8str_sz (const char8_t *const filename, char8_t *const destbuf,
+ size_t *nread)
+{
+ FILE *file;
+
+#if is_win
+ fopen_s (&file, (const char*) filename, "rb,ccs=UTF-8");
+#else
+ file = fopen ((const char*) filename, "rb");
+#endif
+ assTcb (file != nullptr,
+ {
+ fprintf (stderr, "filename:%s\n", filename);
+ });
+
+ i32_t fsz = fsize (file);
+
+ size_t bytes_read = fread (destbuf, 1, fsz, file);
+ assert (bytes_read > 0);
+
+ // null-terminate the buffer
+ destbuf[bytes_read] = 0;
+ *nread = bytes_read;
+ // printf("File content: %s\n", destbuf);
+
+ fclose (file);
+
+ return 0;
+}
+
+void
+zero_trailing_white_char (char8_t *buf, const size_t len)
+{
+ for (size_t i = len - 1; i != 0; i--) // TODO ckd_add
+ {
+ if (buf[i] == '\n' || buf[i] == '\r' || buf[i] == '\t' || buf[i] == ' ')
+ buf[i] = 0;
+ else
+ break;
+ }
+}
+
+void
+zero_trailing_white_char2 (char8_t *buf, size_t *len)
+{
+ for (size_t i = *len - 1; i != 0; i--) // TODO ckd_add
+ {
+ if (buf[i] == '\n' || buf[i] == '\r' || buf[i] == '\t' || buf[i] == ' ')
+ {
+ buf[i] = 0;
+ *len -= 1;
+ }
+ else
+ break;
+ }
+}
+
+#ifdef is_unix
+FILE *
+unix_fd2strm_r (int fd)
+{
+ return fdopen (fd, "r");
+}
+#endif
+
+#ifdef is_win
+FILE *
+win_handle2strm (HANDLE h)
+{
+ int fd = _open_osfhandle ((intptr_t) h, _O_RDONLY);
+ assT (fd > 0);
+
+ FILE *file = _fdopen (fd, "r+");
+ assT (file != nullptr);
+ return file;
+}
+#endif
+
+#ifdef is_win
+int
+win_handle2fd (HANDLE h)
+{
+ int fd = _open_osfhandle ((intptr_t) h, _O_RDONLY);
+ assT (fd > 0);
+ return fd;
+}
+#endif
diff --git a/third_party/mcpc/tst/commtst.h b/third_party/mcpc/tst/commtst.h
new file mode 100644
index 0000000..49dcc93
--- /dev/null
+++ b/third_party/mcpc/tst/commtst.h
@@ -0,0 +1,147 @@
+#ifndef h_commtst
+#define h_commtst
+
+
+// clang-format off
+#ifdef is_unix
+#define _POSIX_SOURCE
+#include <ctype.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <signal.h>
+#include <spawn.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#ifdef is_win
+
+/* # define _WINSOCKAPI_ */
+#include <fcntl.h>
+#include <stdio.h>
+#include <winsock2.h>
+/* # include <windows.h> */
+#include <io.h>
+#include <ws2tcpip.h>
+#include "commtst.h"
+
+#endif
+// clang-format on
+
+#include <mcpc/_c23_keywords.h>
+#include <mcpc/_c23_uchar.h>
+#include <ext_string.h>
+#include <ext_stdint.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ass0(f) \
+ do { \
+ int ec = (f); \
+ if (0 != ec) { \
+ fprintf(stderr, \
+ "%s:%d,ec:%d syserr:%s\n", \
+ __FILE__, \
+ __LINE__, \
+ ec, \
+ strerror(errno)); \
+ abort(); \
+ } \
+ } while (0)
+
+#define assT(f) \
+ do { \
+ if (!(f)) { \
+ fprintf(stderr, \
+ "abort@%s:%d,syserr:%s\n", \
+ __FILE__, \
+ __LINE__, \
+ strerror(errno)); \
+ abort(); \
+ } \
+ } while (0)
+
+#define assF(f) \
+ do { \
+ if ((f)) { \
+ fprintf(stderr, \
+ "abort@%s:%d,syserr:%s\n", \
+ __FILE__, \
+ __LINE__, \
+ strerror(errno)); \
+ abort(); \
+ } \
+ } while (0)
+
+#define assTcb(f, cb) \
+ do { \
+ if (!(f)) { \
+ { cb }; \
+ fprintf(stderr, "abort@%s:%d\n", __FILE__, __LINE__); \
+ abort(); \
+ } \
+ } while (0)
+
+#define BUFCAP 1024
+#define PORT 8080
+
+typedef int8_t i8_t;
+typedef uint8_t u8_t;
+typedef int16_t i16_t;
+typedef uint16_t u16_t;
+typedef int32_t i32_t;
+typedef uint32_t u32_t;
+typedef int64_t i64_t;
+typedef uint64_t u64_t;
+
+typedef char8_t ch8_t;
+
+typedef size_t sz_t;
+
+#ifdef is_win
+typedef SSIZE_T ssize_t;
+#endif
+
+void read_atmost (void *src, char *rbuf, const size_t nexact, size_t *nread);
+
+void recv_atmost (
+#ifdef is_unix
+ int csock,
+#elif defined(is_win)
+ SOCKET cosck,
+#endif
+ char *rbuf, const size_t nmost, size_t *nread);
+
+int32_t read_u8str (const char8_t * const filename, char8_t * const destbuf);
+
+int32_t read_u8str_sz (const char8_t * const filename, char8_t * const destbuf, size_t *nread);
+
+void zero_trailing_white_char (char8_t * buf, const size_t len);
+
+void zero_trailing_white_char2 (char8_t * buf, size_t *len);
+
+
+#ifdef is_unix
+FILE *unix_fd2strm_r (int fd);
+#endif
+
+#ifdef is_win
+FILE *win_handle2strm (HANDLE h);
+#endif
+
+#ifdef is_win
+int win_handle2fd (HANDLE h);
+#endif
+
+
+#endif
diff --git a/third_party/mcpc/tst/itst-svcalc-req-promptslist-1.json b/third_party/mcpc/tst/itst-svcalc-req-promptslist-1.json
new file mode 100644
index 0000000..2431074
--- /dev/null
+++ b/third_party/mcpc/tst/itst-svcalc-req-promptslist-1.json
@@ -0,0 +1 @@
+{"jsonrpc": "2.0", "method": "initialize", "params": {"clientInfo": {"name": "Llama Agent", "version": "0.1"}, "protocolVersion": "2024-11-05", "capabilities": { "tools": { "subscribe": true, "listChanged": true }, "resource": { "subscribe": true, "listChanged": true } }}, "id": 1}{"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}, "id": 8899}{"id":123,"method":"prompts/list","params":[{"obfsarg":"{\"'}}"}]}
diff --git a/third_party/mcpc/tst/itst-svcalc-req-resourceslist-1.json b/third_party/mcpc/tst/itst-svcalc-req-resourceslist-1.json
new file mode 100644
index 0000000..cf23347
--- /dev/null
+++ b/third_party/mcpc/tst/itst-svcalc-req-resourceslist-1.json
@@ -0,0 +1 @@
+{"jsonrpc": "2.0", "method": "initialize", "params": {"clientInfo": {"name": "Llama Agent", "version": "0.1"}, "protocolVersion": "2024-11-05", "capabilities": { "tools": { "subscribe": true, "listChanged": true }, "resource": { "subscribe": true, "listChanged": true } }}, "id": 1}{"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}, "id": 8899}{"id":123,"method":"resources/list","params":[{"obfsarg":"{\"'}}"}]}
diff --git a/third_party/mcpc/tst/itst-svcalc-req-toolslist-1.json b/third_party/mcpc/tst/itst-svcalc-req-toolslist-1.json
new file mode 100644
index 0000000..97be6cf
--- /dev/null
+++ b/third_party/mcpc/tst/itst-svcalc-req-toolslist-1.json
@@ -0,0 +1 @@
+{"jsonrpc": "2.0", "method": "initialize", "params": {"clientInfo": {"name": "Llama Agent", "version": "0.1"}, "protocolVersion": "2024-11-05", "capabilities": { "tools": { "subscribe": true, "listChanged": true }, "resource": { "subscribe": true, "listChanged": true } }}, "id": 1}{"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}, "id": 8899}{"id":123,"method":"tools/list","params":[{"obfsarg":"{\"'}}"}]}
diff --git a/third_party/mcpc/tst/itst-svcalc.c b/third_party/mcpc/tst/itst-svcalc.c
new file mode 100644
index 0000000..3932991
--- /dev/null
+++ b/third_party/mcpc/tst/itst-svcalc.c
@@ -0,0 +1,207 @@
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <mcpc/_c23_keywords.h>
+#include <mcpc/mcpc.h>
+
+#include <ext_string.h>
+
+typedef int8_t i8_t;
+typedef uint8_t u8_t;
+typedef int16_t i16_t;
+typedef uint16_t u16_t;
+typedef int32_t i32_t;
+typedef uint32_t u32_t;
+typedef int64_t i64_t;
+typedef uint64_t u64_t;
+
+static void
+cb_calc (const mcpc_tool_t *tool, mcpc_ucbr_t *ucbr)
+{
+ i32_t a = 0;
+ i32_t b = 0;
+ char8_t op[64];
+ i32_t res = 0;
+
+ assert (0 == mcpc_tool_get_tpropval_i32 (tool, u8c1 ("a"), &a));
+ assert (0 == mcpc_tool_get_tpropval_i32 (tool, u8c1 ("b"), &b));
+ assert (0 == mcpc_tool_get_tpropval_u8str (tool, u8c1 ("op"), op, sizeof (op), nullptr));
+
+ if (false)
+ ;
+ else if (u8streq (op, u8c1 ("+")))
+ {
+ res = a + b;
+ }
+ else if (u8streq (op, u8c1 ("+")))
+ {
+ res = a + b;
+ }
+ else if (u8streq (op, u8c1 ("-")))
+ {
+ res = a - b;
+ }
+ else if (u8streq (op, u8c1 ("*")))
+ {
+ res = a * b;
+ }
+ else if (u8streq (op, u8c1 ("times")))
+ {
+ res = a * b;
+ }
+ else if (u8streq (op, u8c1 ("/")))
+ {
+ res = a / b;
+ }
+ else if (u8streq (op, u8c1 ("div")))
+ {
+ res = a / b;
+ }
+ else
+ {
+ mcpc_ucbr_toolcall_add_errmsg_printf8 (ucbr, u8c1 ("not matching \"any\" operator:%s"), op);
+ return;
+ }
+
+ mcpc_ucbr_toolcall_add_text_printf8 (ucbr, u8c1 ("%d"), res);
+}
+
+mcpc_tool_t *
+new_tool_calc ()
+{
+ const char8_t *name = u8c1 ("calc");
+ const char8_t *desc = u8c1 ("simple calculator");
+ mcpc_tool_t *tool = mcpc_tool_new2 (name, desc);
+
+ {
+ const char8_t *name = u8c1 ("a");
+ const mcpc_anytype_t type = MCPC_I32;
+ const char8_t *desc = u8c1 ("the first number(\"a\") to calculate");
+ mcpc_toolprop_t *prop = mcpc_toolprop_new2 (name, desc, type);
+ mcpc_tool_add_toolprop (tool, prop);
+ }
+ {
+ const char8_t *name = u8c1 ("b");
+ const mcpc_anytype_t type = MCPC_I32;
+ const char8_t *desc = u8c1 ("the second number to calculate");
+ mcpc_toolprop_t *prop = mcpc_toolprop_new2 (name, desc, type);
+ mcpc_tool_add_toolprop (tool, prop);
+ }
+ {
+ const char8_t *name = u8c1 ("op");
+ const mcpc_anytype_t type = MCPC_U8STR;
+ const char8_t *desc = //
+ u8c1 ("the calculating operator, available values " "are: \"+\",'-',\"*\",'/'");
+ mcpc_toolprop_t *prop = mcpc_toolprop_new2 (name, desc, type);
+ mcpc_tool_add_toolprop (tool, prop);
+ }
+
+ mcpc_tool_set_call_cb (tool, &cb_calc);
+
+ return tool;
+}
+
+int
+main_iostrm (int argc, const char *argv[])
+{
+ u8_t in_src = 0; // 1stdin 2file
+ char in_fpath[100];
+
+ for (int i = 2; i < argc; i++)
+ {
+ const char8_t *curr_arg = (const char8_t *) argv[i];
+ const char8_t *nex_arg = nullptr;
+ if (i < argc - 1)
+ nex_arg = (const char8_t *) argv[i + 1];
+ if (nex_arg != nullptr)
+ {
+ if (u8streq (curr_arg, u8c1 ("-i")))
+ {
+ if (u8streq (nex_arg, u8c1 ("-")))
+ {
+ in_src = 1;
+ }
+ else if (beg_with_alph ((const char8_t *) nex_arg))
+ {
+ // TODO nonascii filename?
+ /* memcpy (in_fpath, nex_arg, strlen (nex_arg)); */
+ strcpy (in_fpath, (const char *) nex_arg);
+ in_src = 2;
+ }
+ }
+ }
+ }
+
+ assert (in_src);
+
+ FILE *in_stream = nullptr;
+ switch (in_src)
+ {
+ case 1:
+ in_stream = stdin;
+ break;
+ case 2:
+ in_stream = fopen (in_fpath, "r");
+ if (!in_stream)
+ {
+ fprintf (stdout, "file:%s, err:%s", in_fpath, strerror (errno));
+ }
+ break;
+ }
+ assert (in_stream);
+
+ mcpc_server_t *server = mcpc_server_new_iostrm (in_stream, stdout);
+ mcpc_server_capa_enable_prmt (server);
+ mcpc_server_capa_enable_tool (server);
+ mcpc_server_capa_enable_rsc (server);
+ mcpc_server_capa_enable_complt (server);
+
+ mcpc_tool_t *tool = new_tool_calc ();
+ assert (0 == mcpc_server_add_tool (server, tool));
+
+ mcpc_errcode_t ec = mcpc_server_start (server);
+
+ mcpc_server_close (server);
+ fclose (in_stream);
+
+ return ec;
+}
+
+int
+main_tcp (int argc, const char *argv[])
+{
+ mcpc_server_t *server = mcpc_server_new_tcp ();
+ mcpc_server_capa_enable_prmt (server);
+ mcpc_server_capa_enable_tool (server);
+ mcpc_server_capa_enable_rsc (server);
+ mcpc_server_capa_enable_complt (server);
+
+ mcpc_tool_t *tool = new_tool_calc ();
+ assert (0 == mcpc_server_add_tool (server, tool));
+
+ mcpc_errcode_t ec = mcpc_server_start (server);
+
+ mcpc_server_close (server);
+ return ec;
+}
+
+int
+main (int argc, const char *argv[])
+{
+ assert (argc >= 2);
+ bool as_iostrm = strcmp (argv[1], "iostrm") == 0;
+ bool as_tcp = strcmp (argv[1], "tcp") == 0;
+ assert (as_iostrm || as_tcp);
+ // bool as_iostrm = false;
+ // bool as_tcp = true;
+ if (as_tcp)
+ return main_tcp (argc, argv);
+ if (as_iostrm)
+ return main_iostrm (argc, argv);
+}