summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-06-29 01:28:01 +0900
committertanaka takashi <isazaka@gmail.com>2026-06-29 01:28:01 +0900
commit8d38b06f11c713e547d70468340242f23322152c (patch)
tree566b4c658d7d0157c0cec4da490b37aa747a2d4e
first commit
-rw-r--r--.gitignore82
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt22
-rw-r--r--CMakePresets.json53
-rw-r--r--README.md72
-rw-r--r--cmake/mcpc.cmake53
-rw-r--r--docs/architecture.md21
-rw-r--r--scripts/bootstrap.ps186
-rw-r--r--src/CMakeLists.txt14
-rw-r--r--src/app/main.c15
-rw-r--r--src/domain/README.md8
-rw-r--r--src/mcp/server.c52
-rw-r--r--src/mcp/server.h9
-rw-r--r--src/mcp/tools/health.c29
-rw-r--r--src/mcp/tools/health.h9
-rw-r--r--src/storage/README.md6
-rw-r--r--tests/CMakeLists.txt10
-rw-r--r--tests/fixtures/smoke-input.jsonl5
-rw-r--r--tests/smoke.cmake39
m---------third_party/mcpc0
-rw-r--r--vcpkg.json7
21 files changed, 595 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7b6de5d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,82 @@
+# Build directories
+/build/
+/build-*/
+/cmake-build-*/
+/vcpkg_installed/
+
+# CMake generated files
+CMakeCache.txt
+CMakeFiles/
+CTestTestfile.cmake
+Testing/
+_deps/
+cmake_install.cmake
+compile_commands.json
+install_manifest.txt
+/CMakeUserPresets.json
+
+# Compiled objects and libraries
+*.o
+*.obj
+*.lo
+*.a
+*.lib
+*.so
+*.so.*
+*.dylib
+*.dll
+*.exp
+
+# Executables and debug data
+*.exe
+*.out
+*.app
+*.pdb
+*.ilk
+*.idb
+*.ipdb
+*.iobj
+*.pch
+*.dSYM/
+
+# Generated dependency and coverage data
+*.d
+*.gcda
+*.gcno
+*.gcov
+*.profraw
+*.profdata
+
+# IDE and editor state
+/.vscode/
+/.vs/
+/.idea/
+*.sln
+*.suo
+*.user
+*.userosscache
+*.sdf
+*.opensdf
+*.VC.db
+*.VC.VC.opendb
+*.vcxproj
+*.vcxproj.filters
+*.vcxproj.user
+
+# Local AI-agent configuration and state
+/.agents/
+/.codex/
+/.claude/
+
+# Temporary files and logs
+*.log
+*.tmp
+*.temp
+*.swp
+*.swo
+*~
+
+# Operating-system metadata
+.DS_Store
+Thumbs.db
+Desktop.ini
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..a9d75dc
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "third_party/mcpc"]
+ path = third_party/mcpc
+ url = https://github.com/micl2e2/mcpc.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..0e34b7b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.25)
+
+project(
+ tiny-convmem-mcp
+ VERSION 0.1.0
+ DESCRIPTION "A tiny MCP server for conversation memory"
+ LANGUAGES C
+)
+
+set(CMAKE_C_STANDARD 17)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+
+include(CTest)
+include(cmake/mcpc.cmake)
+
+add_subdirectory(src)
+
+if(BUILD_TESTING)
+ add_subdirectory(tests)
+endif()
+
diff --git a/CMakePresets.json b/CMakePresets.json
new file mode 100644
index 0000000..4ee51a5
--- /dev/null
+++ b/CMakePresets.json
@@ -0,0 +1,53 @@
+{
+ "version": 6,
+ "cmakeMinimumRequired": {
+ "major": 3,
+ "minor": 25,
+ "patch": 0
+ },
+ "configurePresets": [
+ {
+ "name": "windows-vs2026",
+ "displayName": "Windows x64 - Visual Studio 2026",
+ "description": "Configure tiny-convmem-mcp with MSVC and vcpkg manifest mode.",
+ "generator": "Visual Studio 18 2026",
+ "architecture": "x64",
+ "binaryDir": "${sourceDir}/build/windows-vs2026",
+ "cacheVariables": {
+ "BUILD_TESTING": "ON",
+ "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
+ "VCPKG_TARGET_TRIPLET": "x64-windows"
+ }
+ }
+ ],
+ "buildPresets": [
+ {
+ "name": "windows-vs2026-debug",
+ "configurePreset": "windows-vs2026",
+ "configuration": "Debug"
+ },
+ {
+ "name": "windows-vs2026-release",
+ "configurePreset": "windows-vs2026",
+ "configuration": "Release"
+ }
+ ],
+ "testPresets": [
+ {
+ "name": "windows-vs2026-debug",
+ "configurePreset": "windows-vs2026",
+ "configuration": "Debug",
+ "output": {
+ "outputOnFailure": true
+ }
+ },
+ {
+ "name": "windows-vs2026-release",
+ "configurePreset": "windows-vs2026",
+ "configuration": "Release",
+ "output": {
+ "outputOnFailure": true
+ }
+ }
+ ]
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ab9b94c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,72 @@
+# tiny-convmem-mcp
+
+LLMとの会話履歴を扱うための、小さなC言語製MCPサーバです。現在は設計前の
+基盤段階で、stdioによるMCP初期化と`health` toolだけを実装しています。
+
+## 必要環境
+
+- Windows x64
+- Visual Studio Community 2026(Desktop development with C++)
+- CMake 4.3以降
+- Git
+- vcpkg(PATHから`vcpkg`を実行できること)
+
+vcpkgはコンパイラを導入するためではなく、今後SQLite等のライブラリを管理する
+ためにmanifest modeで使用します。現時点の`vcpkg.json`に外部依存はありません。
+
+## 初回構築
+
+PowerShellで次を実行します。スクリプトは不足ツールを自動インストールしません。
+
+```powershell
+git submodule update --init --recursive
+.\scripts\bootstrap.ps1 -Configuration Debug
+```
+
+Releaseも確認する場合は次を実行します。
+
+```powershell
+.\scripts\bootstrap.ps1 -Configuration Release
+```
+
+## 手動ビルドとテスト
+
+presetを直接使う場合は、先にvcpkgルートを設定します。
+
+```powershell
+$config = vcpkg z-print-config | ConvertFrom-Json
+$env:VCPKG_ROOT = $config.'vcpkg-root'
+
+cmake --preset windows-vs2026
+cmake --build --preset windows-vs2026-debug
+ctest --preset windows-vs2026-debug
+```
+
+成果物は`build/windows-vs2026/`以下に生成されます。サーバはstdoutをMCP
+プロトコル専用、stderrを診断メッセージ専用として使用します。
+
+## mcpcの管理
+
+mcpcは`third_party/mcpc`のGit submoduleとしてコミット
+`4025fddfedcfa7573803a47236b147733fcb3c07`へ固定しています。更新時は新しい
+コミットを明示的にcheckoutし、Debug/Releaseのsmoke testを両方実行してください。
+
+```powershell
+git -C third_party/mcpc fetch origin
+git -C third_party/mcpc checkout <reviewed-commit>
+git add third_party/mcpc
+```
+
+固定コミットの上流CMakeには、欠落fixtureの参照、Makefileに対するソース列挙漏れ、
+MSVC互換マクロ名の不整合があります。そのため上流を変更せず、
+`cmake/mcpc.cmake`でMakefileと同じ13ソースを静的ライブラリ化しています。
+
+また、上流は同じ階層に`LICENSE`ファイルと`license/`ディレクトリを保持しており、
+Windowsでは名前が衝突します。`bootstrap.ps1`はライセンス本文を含む`license/`を
+維持し、checkoutできないトップレベル`LICENSE`だけをローカルsubmodule indexで
+`skip-worktree`に設定します。上流の追跡ファイルやコミットは変更しません。
+
+## 現在の境界
+
+会話・メッセージのデータモデル、保持期限、検索方式、ストレージAPI、SQLite採用は
+未決定です。責務の境界は[docs/architecture.md](docs/architecture.md)を参照してください。
diff --git a/cmake/mcpc.cmake b/cmake/mcpc.cmake
new file mode 100644
index 0000000..8f396a7
--- /dev/null
+++ b/cmake/mcpc.cmake
@@ -0,0 +1,53 @@
+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"
+ )
+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.
+set(TINY_CONVMEM_MCPC_SOURCES
+ "${TINY_CONVMEM_MCPC_ROOT}/src/alloc.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/log.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/errcode.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/anydata.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/tool.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/rsc.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/prmpt.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/server.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/retbuf.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/ucbr.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/complt.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/serlz.c"
+ "${TINY_CONVMEM_MCPC_ROOT}/src/mjson.c"
+)
+
+add_library(mcpc STATIC ${TINY_CONVMEM_MCPC_SOURCES})
+add_library(mcpc::mcpc ALIAS mcpc)
+
+target_include_directories(mcpc
+ SYSTEM PUBLIC "${TINY_CONVMEM_MCPC_ROOT}"
+ PRIVATE "${TINY_CONVMEM_MCPC_ROOT}/src"
+)
+target_compile_features(mcpc PUBLIC c_std_17)
+
+if(MSVC)
+ # These compatibility definitions affect mcpc's public headers as well as
+ # its implementation, so consumers must compile with the same view.
+ target_compile_definitions(mcpc PUBLIC
+ is_win=1
+ is_wincl=1
+ MCPC_C23PTCH_KW1
+ MCPC_C23PTCH_CKD1
+ MCPC_C23PTCH_UCHAR1
+ MCPC_C23GIVUP_FIXENUM
+ )
+ target_compile_options(mcpc PRIVATE /utf-8)
+ target_link_libraries(mcpc PUBLIC ws2_32)
+else()
+ message(WARNING
+ "Only Windows/MSVC is currently verified. Other toolchains are future work."
+ )
+endif()
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..f23f06a
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,21 @@
+# Architecture
+
+`tiny-convmem-mcp` currently proves only the MCP integration boundary. The
+executable composes an mcpc stdio server and registers a single `health` tool.
+
+Future conversation-memory work is split into these dependencies:
+
+```text
+MCP transport and tools -> domain contracts <- storage adapters
+```
+
+- `src/mcp` translates MCP requests and responses. It may depend on mcpc and
+ domain contracts, but it must not contain persistence logic.
+- `src/domain` will own conversation and message concepts without depending on
+ mcpc or a database.
+- `src/storage` will implement domain persistence contracts using a storage
+ engine selected in the next design phase.
+
+No conversation schema, retention policy, search behavior, or storage engine is
+implied by the current scaffold.
+
diff --git a/scripts/bootstrap.ps1 b/scripts/bootstrap.ps1
new file mode 100644
index 0000000..d756159
--- /dev/null
+++ b/scripts/bootstrap.ps1
@@ -0,0 +1,86 @@
+[CmdletBinding()]
+param(
+ [ValidateSet("Debug", "Release")]
+ [string]$Configuration = "Debug"
+)
+
+$ErrorActionPreference = "Stop"
+$ProjectRoot = Split-Path -Parent $PSScriptRoot
+
+function Require-Command {
+ param([Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$InstallHint)
+
+ $command = Get-Command $Name -ErrorAction SilentlyContinue
+ if (-not $command) {
+ throw "Required command '$Name' was not found. $InstallHint"
+ }
+ return $command
+}
+
+function Invoke-Checked {
+ param([Parameter(Mandatory)][string]$Command, [Parameter(ValueFromRemainingArguments)][string[]]$Arguments)
+
+ & $Command @Arguments
+ if ($LASTEXITCODE -ne 0) {
+ throw "Command failed with exit code ${LASTEXITCODE}: $Command $($Arguments -join ' ')"
+ }
+}
+
+Require-Command git "Install Git for Windows and add it to PATH." | Out-Null
+Require-Command cmake "Install CMake 4.3 or newer and add it to PATH." | Out-Null
+Require-Command vcpkg "Install vcpkg and add it to PATH." | Out-Null
+
+$cmakeVersionText = (& cmake --version | Select-Object -First 1)
+if ($cmakeVersionText -notmatch '(\d+)\.(\d+)\.(\d+)') {
+ throw "Could not determine the CMake version from: $cmakeVersionText"
+}
+$cmakeVersion = [Version]::new([int]$Matches[1], [int]$Matches[2], [int]$Matches[3])
+if ($cmakeVersion -lt [Version]'4.3.0') {
+ throw "CMake 4.3 or newer is required; found $cmakeVersion."
+}
+
+$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
+if (-not (Test-Path $vswhere)) {
+ throw "vswhere.exe was not found. Install Visual Studio 2026 with the Desktop development with C++ workload."
+}
+$vsPath = & $vswhere -latest -products * -version '[18.0,19.0)' `
+ -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
+if (-not $vsPath) {
+ throw "Visual Studio 2026 with the MSVC x64 toolchain was not found. Add the Desktop development with C++ workload."
+}
+
+$vcpkgConfig = (& vcpkg z-print-config | ConvertFrom-Json)
+$env:VCPKG_ROOT = $vcpkgConfig.'vcpkg-root'
+$toolchain = Join-Path $env:VCPKG_ROOT 'scripts\buildsystems\vcpkg.cmake'
+if (-not (Test-Path $toolchain)) {
+ throw "The vcpkg CMake toolchain was not found at $toolchain."
+}
+
+Push-Location $ProjectRoot
+try {
+ if (-not (Test-Path 'third_party\mcpc\mcpc\mcpc.h')) {
+ Invoke-Checked git submodule update --init --recursive
+ }
+
+ # mcpc tracks both a top-level LICENSE file and a license/ directory.
+ # They collide on Windows' case-insensitive filesystem. Preserve the
+ # license/ directory used by the project and suppress only the impossible
+ # top-level checkout entry in the local submodule index.
+ $licenseStatus = & git -C third_party/mcpc status --porcelain -- LICENSE 2>$null
+ if ($LASTEXITCODE -eq 0 -and $licenseStatus -match '^ D LICENSE$') {
+ & git -C third_party/mcpc update-index --skip-worktree LICENSE
+ if ($LASTEXITCODE -ne 0) {
+ Write-Warning "Could not suppress mcpc's Windows LICENSE case collision; the build can continue, but the submodule may appear dirty."
+ }
+ }
+
+ Invoke-Checked cmake --preset windows-vs2026
+ $preset = "windows-vs2026-$($Configuration.ToLowerInvariant())"
+ Invoke-Checked cmake --build --preset $preset
+ Invoke-Checked ctest --preset $preset
+}
+finally {
+ Pop-Location
+}
+
+Write-Host "tiny-convmem-mcp $Configuration build and smoke test completed successfully."
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..ef54a08
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_executable(tiny-convmem-mcp
+ app/main.c
+ mcp/server.c
+ mcp/tools/health.c
+)
+
+target_include_directories(tiny-convmem-mcp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
+target_link_libraries(tiny-convmem-mcp PRIVATE mcpc::mcpc)
+target_compile_features(tiny-convmem-mcp PRIVATE c_std_17)
+
+if(MSVC)
+ target_compile_options(tiny-convmem-mcp PRIVATE /utf-8 /W4)
+endif()
+
diff --git a/src/app/main.c b/src/app/main.c
new file mode 100644
index 0000000..ab8688f
--- /dev/null
+++ b/src/app/main.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+
+#include "mcp/server.h"
+
+int
+main(void)
+{
+ const int result = tiny_convmem_server_run(stdin, stdout);
+ if (result != 0)
+ {
+ fprintf(stderr, "tiny-convmem-mcp stopped with error code %d\n", result);
+ }
+ return result;
+}
+
diff --git a/src/domain/README.md b/src/domain/README.md
new file mode 100644
index 0000000..0d359af
--- /dev/null
+++ b/src/domain/README.md
@@ -0,0 +1,8 @@
+# Domain layer
+
+This directory is reserved for the conversation-memory domain model and its
+business rules. Conversation, message, retention, and search contracts will be
+defined here only after the next design phase.
+
+The domain layer must not depend on mcpc or on a concrete storage engine.
+
diff --git a/src/mcp/server.c b/src/mcp/server.c
new file mode 100644
index 0000000..57d197d
--- /dev/null
+++ b/src/mcp/server.c
@@ -0,0 +1,52 @@
+#include "mcp/server.h"
+
+#include <mcpc/mcpc.h>
+#include "mcp/tools/health.h"
+
+int
+tiny_convmem_server_run(FILE *input, FILE *output)
+{
+ mcpc_server_t *server = mcpc_server_new_iostrm(input, output);
+ if (server == NULL)
+ {
+ return MCPC_EC_BUG;
+ }
+
+ mcpc_errcode_t result = mcpc_server_set_nament(
+ server,
+ (const char8_t *)"tiny-convmem-mcp"
+ );
+ if (result == MCPC_EC_0)
+ {
+ result = mcpc_server_capa_enable_tool(server);
+ }
+
+ mcpc_tool_t *health = NULL;
+ if (result == MCPC_EC_0)
+ {
+ health = tiny_convmem_health_tool_create();
+ if (health == NULL)
+ {
+ result = MCPC_EC_BUG;
+ }
+ }
+
+ if (result == MCPC_EC_0)
+ {
+ result = mcpc_server_add_tool(server, health);
+ }
+ if (result == MCPC_EC_0)
+ {
+ result = mcpc_server_start(server);
+ }
+
+ {
+ const mcpc_errcode_t close_result = mcpc_server_close(server);
+ if (result == MCPC_EC_0)
+ {
+ result = close_result;
+ }
+ }
+
+ return (int)result;
+}
diff --git a/src/mcp/server.h b/src/mcp/server.h
new file mode 100644
index 0000000..e3ff2c4
--- /dev/null
+++ b/src/mcp/server.h
@@ -0,0 +1,9 @@
+#ifndef TINY_CONVMEM_MCP_SERVER_H
+#define TINY_CONVMEM_MCP_SERVER_H
+
+#include <stdio.h>
+
+int tiny_convmem_server_run(FILE *input, FILE *output);
+
+#endif
+
diff --git a/src/mcp/tools/health.c b/src/mcp/tools/health.c
new file mode 100644
index 0000000..0309ea5
--- /dev/null
+++ b/src/mcp/tools/health.c
@@ -0,0 +1,29 @@
+#include "mcp/tools/health.h"
+
+#include <mcpc/ucbr.h>
+
+static void
+health_call(const mcpc_tool_t *tool, mcpc_ucbr_t *result)
+{
+ (void)tool;
+ mcpc_ucbr_toolcall_add_text_printf8(result, (const char8_t *)"ok");
+}
+
+mcpc_tool_t *
+tiny_convmem_health_tool_create(void)
+{
+ mcpc_tool_t *tool = mcpc_tool_new2(
+ (const char8_t *)"health",
+ (const char8_t *)"Check whether the tiny conversation memory server is running"
+ );
+ if (tool == NULL)
+ {
+ return NULL;
+ }
+
+ if (mcpc_tool_set_call_cb(tool, health_call) != MCPC_EC_0)
+ {
+ return NULL;
+ }
+ return tool;
+}
diff --git a/src/mcp/tools/health.h b/src/mcp/tools/health.h
new file mode 100644
index 0000000..f4bfe0a
--- /dev/null
+++ b/src/mcp/tools/health.h
@@ -0,0 +1,9 @@
+#ifndef TINY_CONVMEM_MCP_HEALTH_H
+#define TINY_CONVMEM_MCP_HEALTH_H
+
+#include <mcpc/tool.h>
+
+mcpc_tool_t *tiny_convmem_health_tool_create(void);
+
+#endif
+
diff --git a/src/storage/README.md b/src/storage/README.md
new file mode 100644
index 0000000..ceb8965
--- /dev/null
+++ b/src/storage/README.md
@@ -0,0 +1,6 @@
+# Storage layer
+
+This directory is reserved for persistence adapters. The storage engine and its
+schema are intentionally undecided; SQLite or another dependency will be added
+to `vcpkg.json` only after the storage contract is designed.
+
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..8f79a91
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_test(
+ NAME tiny-convmem-mcp.smoke
+ COMMAND "${CMAKE_COMMAND}"
+ "-DSERVER=$<TARGET_FILE:tiny-convmem-mcp>"
+ "-DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/fixtures/smoke-input.jsonl"
+ -P "${CMAKE_CURRENT_SOURCE_DIR}/smoke.cmake"
+)
+
+set_tests_properties(tiny-convmem-mcp.smoke PROPERTIES TIMEOUT 15)
+
diff --git a/tests/fixtures/smoke-input.jsonl b/tests/fixtures/smoke-input.jsonl
new file mode 100644
index 0000000..5c046a4
--- /dev/null
+++ b/tests/fixtures/smoke-input.jsonl
@@ -0,0 +1,5 @@
+{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"tiny-convmem-smoke","version":"0.1.0"}}}
+{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
+{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
+{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"health","arguments":{}}}
+
diff --git a/tests/smoke.cmake b/tests/smoke.cmake
new file mode 100644
index 0000000..b741d4b
--- /dev/null
+++ b/tests/smoke.cmake
@@ -0,0 +1,39 @@
+if(NOT DEFINED SERVER OR NOT EXISTS "${SERVER}")
+ message(FATAL_ERROR "SERVER does not name a built tiny-convmem-mcp executable")
+endif()
+if(NOT DEFINED INPUT OR NOT EXISTS "${INPUT}")
+ message(FATAL_ERROR "INPUT does not name the smoke-test fixture")
+endif()
+
+execute_process(
+ COMMAND "${SERVER}"
+ INPUT_FILE "${INPUT}"
+ OUTPUT_VARIABLE server_output
+ ERROR_VARIABLE server_error
+ RESULT_VARIABLE server_result
+ TIMEOUT 10
+)
+
+if(NOT server_result EQUAL 0)
+ message(FATAL_ERROR
+ "Server exited with ${server_result}.\nstdout: ${server_output}\nstderr: ${server_error}"
+ )
+endif()
+
+foreach(expected
+ "\"protocolVersion\":\"2024-11-05\""
+ "\"serverInfo\""
+ "\"capabilities\""
+ "\"name\":\"health\""
+ "\"type\":\"text\""
+ "\"text\":\"ok\""
+ )
+ string(FIND "${server_output}" "${expected}" match_position)
+ if(match_position EQUAL -1)
+ message(FATAL_ERROR
+ "Expected ${expected} in MCP output.\nstdout: ${server_output}\nstderr: ${server_error}"
+ )
+ endif()
+endforeach()
+
+message(STATUS "MCP initialize, tools/list, and health tools/call succeeded")
diff --git a/third_party/mcpc b/third_party/mcpc
new file mode 160000
+Subproject 4025fddfedcfa7573803a47236b147733fcb3c0
diff --git a/vcpkg.json b/vcpkg.json
new file mode 100644
index 0000000..accbc81
--- /dev/null
+++ b/vcpkg.json
@@ -0,0 +1,7 @@
+{
+ "name": "tiny-convmem-mcp",
+ "version-string": "0.1.0",
+ "description": "A tiny MCP server for conversation memory",
+ "dependencies": []
+}
+