Skip to content

Commit 3884fb4

Browse files
authored
/docs renders OpenAPI docs (#61)
* /docs renders OpenAPI docs * e.g. http://localhost:8090/docs * BREAKING: API: changed route for listing tools schemas for a server to: /servers/{server_name}/tools * BREAKING API: changed routes for tools to: /servers/{server_name}/tools/{tool_name} * Docs updates * Refactored code, introduced huma. * Register routes and use Operations to define the routes better for OpenAPI docs
1 parent a2e8ee2 commit 3884fb4

File tree

19 files changed

+692
-502
lines changed

19 files changed

+692
-502
lines changed

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: build clean docs docs-cli docs-nav docs-local install test uninstall
1+
.PHONY: build clean docs docs-cli docs-local docs-nav install test uninstall
22

33
MODULE_PATH := github.com/mozilla-ai/mcpd/v2
44

@@ -34,25 +34,25 @@ clean:
3434
@# Remove the built executable and any temporary files
3535
@rm -f mcpd # The executable itself
3636
@# Add any other build artifacts here if they accumulate (e.g., cache files)
37-
@echo "Build artifacts cleaned"
37+
@echo "build artifacts cleaned"
3838

3939
uninstall:
4040
@# Remove the installed executable from the system
4141
@# Requires sudo if INSTALL_DIR is a system path
4242
@rm -f $(INSTALL_DIR)/mcpd
4343
@echo "mcpd uninstalled from $(INSTALL_DIR)/mcpd"
4444

45-
## Runs MkDocs locally
45+
# Runs MkDocs locally
4646
docs: docs-local
4747

48-
## Runs MkDocs locally
48+
# Runs MkDocs locally
4949
docs-local: docs-nav
5050
@uv venv && \
5151
source .venv/bin/activate && \
5252
uv pip install mkdocs mkdocs-material && \
5353
uv run mkdocs serve
5454

55-
## Generates CLI markdown documentation
55+
# Generates CLI markdown documentation
5656
docs-cli:
5757
@go run -tags=docsgen_cli ./tools/docsgen/cli/cmds.go
5858
@echo "mcpd CLI command documentation generated"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ Covers setup, CLI usage, configuration, secrets, the daemon, Makefile commands,
2727
Install dependencies:
2828

2929
- [Go](https://go.dev/doc/install)
30-
- [uvx](https://docs.astral.sh/uv/getting-started/installation/)
3130
- [npx](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
31+
- [uvx](https://docs.astral.sh/uv/getting-started/installation/)
3232

3333
Clone the repo:
3434
```bash
@@ -63,6 +63,8 @@ Start the daemon:
6363
mcpd daemon
6464
```
6565

66+
API docs will be available at `/docs`, e.g. `http://localhost:8090/docs`
67+
6668
## 🧰 Development
6769

6870
Run tests:

docs/requirements.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
To use `mcpd`, ensure the following tools are installed:
44

5-
- [`uv` / `uvx`](https://docs.astral.sh/uv/getting-started/installation/) - for running `uvx` Python packages
6-
- [`npx` / `npm` / `NodeJS`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - for running JavaScript/TypeScript packages
7-
- [Go](https://go.dev/doc/install) - only required for building and developing `mcpd`
5+
| Tool | Purpose | Notes |
6+
|----------------|------------------------------------------------|-------------------------------------------------------------------|
7+
| `Go >= 1.24.4` | Required for building `mcpd` and running tests | https://go.dev/doc/install |
8+
| `uv` | for running `uvx` Python packages | https://docs.astral.sh/uv/getting-started/installation/ |
9+
| `npx` | for running JavaScript/TypeScript packages | https://docs.npmjs.com/downloading-and-installing-node-js-and-npm |
810

911
!!! note "Internet Connectivity"
1012
`mcpd` requires internet access to contact package registries and to allow MCP servers access to the internet if required when running.

docs/tutorial.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ mcpd daemon
5454
```
5555

5656
!!! note "API Endpoint"
57-
The API will be available at `http://localhost:8090/api/v1/servers`
57+
The API docs will be available at `http://localhost:8090/docs`
5858

5959
---
6060

@@ -73,5 +73,5 @@ Make a request to a tool on a specific MCP server:
7373
```bash
7474
curl -s -X POST -H "Content-Type: application/json" \\
7575
-d '{"timezone": "America/New_York"}' \\
76-
http://localhost:8090/api/v1/servers/time/get_current_time | jq
76+
http://localhost:8090/api/v1/servers/time/tools/get_current_time | jq
7777
```

go.mod

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,29 @@ go 1.24.4
44

55
require (
66
github.com/BurntSushi/toml v1.5.0
7+
github.com/danielgtaylor/huma/v2 v2.34.1
78
github.com/go-chi/chi/v5 v5.2.2
89
github.com/hashicorp/go-hclog v1.6.3
9-
github.com/mark3labs/mcp-go v0.31.0
10+
github.com/mark3labs/mcp-go v0.32.0
1011
github.com/spf13/cobra v1.9.1
1112
github.com/spf13/pflag v1.0.6
12-
github.com/stretchr/testify v1.9.0
13+
github.com/stretchr/testify v1.10.0
1314
gopkg.in/yaml.v3 v3.0.1
1415
)
1516

1617
require (
17-
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
18+
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
1819
github.com/davecgh/go-spew v1.1.1 // indirect
19-
github.com/fatih/color v1.13.0 // indirect
20+
github.com/fatih/color v1.18.0 // indirect
2021
github.com/google/uuid v1.6.0 // indirect
2122
github.com/inconshreveable/mousetrap v1.1.0 // indirect
22-
github.com/mattn/go-colorable v0.1.12 // indirect
23-
github.com/mattn/go-isatty v0.0.14 // indirect
24-
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
23+
github.com/mattn/go-colorable v0.1.14 // indirect
24+
github.com/mattn/go-isatty v0.0.20 // indirect
2525
github.com/pmezard/go-difflib v1.0.0 // indirect
26+
github.com/rogpeppe/go-internal v1.11.0 // indirect
2627
github.com/russross/blackfriday/v2 v2.1.0 // indirect
27-
github.com/spf13/cast v1.7.1 // indirect
28+
github.com/spf13/cast v1.9.2 // indirect
2829
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
29-
golang.org/x/sys v0.18.0 // indirect
30-
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
30+
golang.org/x/sys v0.33.0 // indirect
31+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
3132
)

go.sum

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,73 @@
11
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
22
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
3-
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
43
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
4+
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
5+
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
6+
github.com/danielgtaylor/huma/v2 v2.34.1 h1:EmOJAbzEGfy0wAq/QMQ1YKfEMBEfE94xdBRLPBP0gwQ=
7+
github.com/danielgtaylor/huma/v2 v2.34.1/go.mod h1:ynwJgLk8iGVgoaipi5tgwIQ5yoFNmiu+QdhU7CEEmhk=
58
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
69
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
710
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8-
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
911
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
12+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
13+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
1014
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
1115
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
1216
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
1317
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
14-
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
15-
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
18+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
19+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1620
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1721
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1822
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
1923
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
2024
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
2125
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
26+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
2227
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
2328
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
2429
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
2530
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
2631
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2732
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
28-
github.com/mark3labs/mcp-go v0.31.0 h1:4UxSV8aM770OPmTvaVe/b1rA2oZAjBMhGBfUgOGut+4=
29-
github.com/mark3labs/mcp-go v0.31.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
33+
github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
34+
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
3035
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
31-
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
3236
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
37+
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
38+
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
3339
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
34-
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
3540
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
36-
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
37-
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
41+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
42+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
3843
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3944
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
40-
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
41-
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
45+
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
46+
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
4247
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
4348
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
44-
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
45-
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
49+
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
50+
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
4651
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
4752
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
4853
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
4954
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
5055
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
5156
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
52-
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
53-
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
57+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
58+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
5459
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
5560
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
5661
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5762
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5863
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5964
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6065
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
61-
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
62-
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
66+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
67+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
68+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
6369
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
64-
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
65-
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
70+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
71+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
6672
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
6773
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/api/convert.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package api
2+
3+
type Convertible[T any] interface {
4+
ToAPIType() T
5+
}

internal/api/health.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"slices"
7+
"strings"
8+
"time"
9+
10+
"github.com/danielgtaylor/huma/v2"
11+
12+
"github.com/mozilla-ai/mcpd/v2/internal/contracts"
13+
"github.com/mozilla-ai/mcpd/v2/internal/domain"
14+
)
15+
16+
const (
17+
HealthStatusOK HealthStatus = "ok"
18+
HealthStatusTimeout HealthStatus = "timeout"
19+
HealthStatusUnreachable HealthStatus = "unreachable"
20+
HealthStatusUnknown HealthStatus = "unknown"
21+
)
22+
23+
// DomainServerHealth is a wrapper that allows receivers to be declared in the API package that deal with domain types.
24+
type DomainServerHealth domain.ServerHealth
25+
26+
// HealthStatus represents the current status of a particular MCP server when establishing its health.
27+
type HealthStatus string
28+
29+
// ServerHealth is used to provide information about ongoing health checks that are performed on running MCP servers.
30+
type ServerHealth struct {
31+
Name string `json:"name"`
32+
Status HealthStatus `json:"status"`
33+
Latency *string `json:"latency,omitempty"`
34+
LastChecked *time.Time `json:"last_checked,omitempty"`
35+
LastSuccessful *time.Time `json:"last_successful,omitempty"`
36+
}
37+
38+
// ServersHealth represents a collection of ServerHealth.
39+
type ServersHealth struct {
40+
Servers []ServerHealth `json:"servers"`
41+
}
42+
43+
// ServersHealthResponse is the response for GET /health
44+
type ServersHealthResponse struct {
45+
Body struct {
46+
Servers []ServerHealth `json:"servers" doc:"Tracked MCP server health statuses"`
47+
}
48+
}
49+
50+
// ServerHealthRequest represents the incoming request for obtaining ServerHealth.
51+
type ServerHealthRequest struct {
52+
Name string `path:"name" example:"time" doc:"Name of the server to check"`
53+
}
54+
55+
// ServerHealthResponse represents the wrapped API response for a ServerHealth.
56+
type ServerHealthResponse struct {
57+
Body ServerHealth
58+
}
59+
60+
// ToAPIType can be used to convert a wrapped domain type to an API-safe type.
61+
func (d DomainServerHealth) ToAPIType() ServerHealth {
62+
var latency *string
63+
if d.Latency != nil {
64+
s := d.Latency.String()
65+
latency = &s
66+
}
67+
return ServerHealth{
68+
Name: d.Name,
69+
Status: HealthStatus(d.Status), // TODO: Validation?
70+
Latency: latency,
71+
LastChecked: d.LastChecked,
72+
LastSuccessful: d.LastSuccessful,
73+
}
74+
}
75+
76+
// RegisterHealthRoutes sets up health-related API endpoint routes.
77+
func RegisterHealthRoutes(routerAPI huma.API, monitor contracts.MCPHealthMonitor, apiPathPrefix string) {
78+
healthAPI := huma.NewGroup(routerAPI, apiPathPrefix)
79+
tags := []string{"Health"}
80+
81+
huma.Register(
82+
healthAPI,
83+
huma.Operation{
84+
OperationID: "listServersHealth",
85+
Method: http.MethodGet,
86+
Path: "/",
87+
Summary: "List the health statuses for all servers",
88+
Tags: tags,
89+
},
90+
func(ctx context.Context, _ *struct{}) (*ServersHealthResponse, error) {
91+
return handleHealthServers(monitor)
92+
},
93+
)
94+
95+
huma.Register(
96+
healthAPI,
97+
huma.Operation{
98+
OperationID: "getServerHealth",
99+
Method: http.MethodGet,
100+
Path: "/servers/{name}",
101+
Summary: "Get the health status of a server",
102+
Tags: tags,
103+
},
104+
func(ctx context.Context, input *ServerHealthRequest) (*ServerHealthResponse, error) {
105+
return handleHealthServer(monitor, input.Name)
106+
},
107+
)
108+
}
109+
110+
// handleHealthServers is the handler for retrieving the current health for all registered MCP servers.
111+
func handleHealthServers(monitor contracts.MCPHealthMonitor) (*ServersHealthResponse, error) {
112+
servers := monitor.List()
113+
114+
slices.SortFunc(servers, func(a, b domain.ServerHealth) int {
115+
return strings.Compare(a.Name, b.Name)
116+
})
117+
118+
apiServers := make([]ServerHealth, 0, len(servers))
119+
for _, s := range servers {
120+
apiServers = append(apiServers, DomainServerHealth(s).ToAPIType())
121+
}
122+
123+
resp := &ServersHealthResponse{}
124+
resp.Body.Servers = apiServers
125+
126+
return resp, nil
127+
}
128+
129+
// handleHealthServer is the handler for retrieving the current health the specified registered MCP server.
130+
func handleHealthServer(monitor contracts.MCPHealthMonitor, name string) (*ServerHealthResponse, error) {
131+
health, err := monitor.Status(name)
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
response := ServerHealthResponse{}
137+
response.Body = DomainServerHealth(health).ToAPIType()
138+
139+
return &response, nil
140+
}

0 commit comments

Comments
 (0)