This is page 1 of 8. Use http://codebase.md/tuananh/hyper-mcp?page={x} to view the full context.
# Directory Structure
```
├── .cursor
│ └── rules
│ └── print-ctx-size.mdc
├── .dockerignore
├── .github
│ ├── renovate.json5
│ └── workflows
│ ├── ci.yml
│ ├── nightly.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── .hadolint.yaml
├── .pre-commit-config.yaml
├── .windsurf
│ └── rules
│ ├── print-ctx-size.md
│ └── think.md
├── assets
│ ├── cursor-mcp-1.png
│ ├── cursor-mcp.png
│ ├── eval-py.jpg
│ └── logo.png
├── Cargo.lock
├── Cargo.toml
├── config.example.json
├── config.example.yaml
├── CREATING_PLUGINS.md
├── DEPLOYMENT.md
├── Dockerfile
├── examples
│ └── plugins
│ ├── v1
│ │ ├── arxiv
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── context7
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── crates-io
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── crypto-price
│ │ │ ├── Dockerfile
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── main.go
│ │ │ ├── pdk.gen.go
│ │ │ └── README.md
│ │ ├── eval-py
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── fetch
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── fs
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── github
│ │ │ ├── .gitignore
│ │ │ ├── branches.go
│ │ │ ├── Dockerfile
│ │ │ ├── files.go
│ │ │ ├── gists.go
│ │ │ ├── go.mod
│ │ │ ├── go.sum
│ │ │ ├── issues.go
│ │ │ ├── main.go
│ │ │ ├── pdk.gen.go
│ │ │ ├── README.md
│ │ │ └── repo.go
│ │ ├── gitlab
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── gomodule
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── hash
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.lock
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── maven
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── meme-generator
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── generate_embedded.py
│ │ │ ├── README.md
│ │ │ ├── src
│ │ │ │ ├── embedded.rs
│ │ │ │ ├── lib.rs
│ │ │ │ └── pdk.rs
│ │ │ └── templates.json
│ │ ├── memory
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── myip
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.lock
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── qdrant
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ ├── pdk.rs
│ │ │ └── qdrant_client.rs
│ │ ├── qr-code
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.lock
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── serper
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── sqlite
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── think
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ ├── time
│ │ │ ├── .cargo
│ │ │ │ └── config.toml
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ ├── src
│ │ │ │ ├── lib.rs
│ │ │ │ └── pdk.rs
│ │ │ └── time.wasm
│ │ └── tool-list-changed
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── lib.rs
│ │ │ └── pdk.rs
│ │ └── tool_list_changed.wasm
│ └── v2
│ └── rstime
│ ├── .cargo
│ │ └── config.toml
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Dockerfile
│ ├── README.md
│ ├── rstime.wasm
│ └── src
│ ├── lib.rs
│ └── pdk
│ ├── exports.rs
│ ├── imports.rs
│ ├── mod.rs
│ └── types.rs
├── iac
│ ├── .terraform.lock.hcl
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── justfile
├── LICENSE
├── README.md
├── RUNTIME_CONFIG.md
├── rust-toolchain.toml
├── server.json
├── SKIP_TOOLS_GUIDE.md
├── src
│ ├── cli.rs
│ ├── config.rs
│ ├── https_auth.rs
│ ├── logging.rs
│ ├── main.rs
│ ├── naming.rs
│ ├── plugin.rs
│ ├── service.rs
│ └── wasm
│ ├── http.rs
│ ├── mod.rs
│ ├── oci.rs
│ └── s3.rs
├── templates
│ └── plugins
│ ├── go
│ │ ├── .gitignore
│ │ ├── Dockerfile
│ │ ├── exports.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── imports.go
│ │ ├── main.go
│ │ ├── README.md
│ │ └── types.go
│ ├── README.md
│ └── rust
│ ├── .cargo
│ │ └── config.toml
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Dockerfile
│ ├── README.md
│ └── src
│ ├── lib.rs
│ └── pdk
│ ├── exports.rs
│ ├── imports.rs
│ ├── mod.rs
│ └── types.rs
├── tests
│ └── fixtures
│ ├── config_with_auths.json
│ ├── config_with_auths.yaml
│ ├── documentation_example.json
│ ├── documentation_example.yaml
│ ├── invalid_auth_config.yaml
│ ├── invalid_plugin_name.yaml
│ ├── invalid_structure.yaml
│ ├── invalid_url.yaml
│ ├── keyring_auth_config.yaml
│ ├── skip_tools_examples.yaml
│ ├── unsupported_config.txt
│ ├── valid_config.json
│ └── valid_config.yaml
└── xtp-plugin-schema.json
```
# Files
--------------------------------------------------------------------------------
/examples/plugins/v1/github/.gitignore:
--------------------------------------------------------------------------------
```
dist/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/hash/.gitignore:
--------------------------------------------------------------------------------
```
/target
```
--------------------------------------------------------------------------------
/examples/plugins/v1/myip/.gitignore:
--------------------------------------------------------------------------------
```
/target
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qr-code/.gitignore:
--------------------------------------------------------------------------------
```
/target
```
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
```
[submodule "examples/plugins/v1/meme-generator/assets"]
path = examples/plugins/v1/meme-generator/assets
url = https://github.com/tuananh/meme-generator-assets.git
```
--------------------------------------------------------------------------------
/templates/plugins/go/.gitignore:
--------------------------------------------------------------------------------
```
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.so.*
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool
*.out
# Dependency directories
vendor/
# Go workspace file
go.work
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store
# Build output
/bin/
/dist/
plugin.wasm
```
--------------------------------------------------------------------------------
/.hadolint.yaml:
--------------------------------------------------------------------------------
```yaml
# Output & failure behavior
format: tty # default output format
failure-threshold: warning # default: fail on >= info
no-color: false
no-fail: false # exit non-zero when threshold is met
# Rule configuration
ignored: [] # no rules ignored by default
override: # no severity overrides by default
error: []
warning: []
info: []
style: []
# Labels
label-schema: {} # no required labels by default
strict-labels: false # only check labels in schema when true
# Misc
disable-ignore-pragma: false # allow inline `# hadolint ignore=...`
trustedRegistries: [] # none by default
```
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
```yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
args: ["--maxkb=2000"]
- id: check-json
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/backplane/pre-commit-rust-hooks
rev: v1.2.1
hooks:
- id: fmt
args: ["--"]
- id: check
- id: clippy
args: ["--", "-D warnings"]
- id: test
stages: [manual]
args: ["--workspace", "--all-features"]
- repo: https://github.com/hadolint/hadolint
rev: v2.14.0
hooks:
- id: hadolint
stages: [manual]
args: ["--no-color"]
```
--------------------------------------------------------------------------------
/examples/plugins/v1/arxiv/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/context7/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crates-io/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/eval-py/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fetch/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fs/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gitlab/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gomodule/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/maven/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/memory/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/serper/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/sqlite/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/think/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/time/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v1/tool-list-changed/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/templates/plugins/rust/.gitignore:
--------------------------------------------------------------------------------
```
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
```
# Created by https://www.toptal.com/developers/gitignore/api/linux,rust,terraform
# Edit at https://www.toptal.com/developers/gitignore?templates=linux,rust,terraform
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
### Terraform ###
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# End of https://www.toptal.com/developers/gitignore/api/linux,rust,terraform
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Created by https://www.toptal.com/developers/gitignore/api/linux,rust,terraform
# Edit at https://www.toptal.com/developers/gitignore?templates=linux,rust,terraform
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
### Terraform ###
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Ignore .DS_Store files, which are created by macOS Finder
.DS_Store
# End of https://www.toptal.com/developers/gitignore/api/linux,rust,terraform
```
--------------------------------------------------------------------------------
/iac/.terraform.lock.hcl:
--------------------------------------------------------------------------------
```
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/google" {
version = "4.85.0"
constraints = ">= 3.53.0, < 5.0.0"
hashes = [
"h1:aSRZcEKF2wOi/v24IA+k9J2Y7aKVV1cHi/R0V3EhxXQ=",
"zh:17d60a6a6c1741cf1e09ac6731433a30950285eac88236e623ab4cbf23832ca3",
"zh:1c70254c016439dbb75cab646b4beace6ceeff117c75d81f2cc27d41c312f752",
"zh:35e2aa2cc7ac84ce55e05bb4de7b461b169d3582e56d3262e249ff09d64fe008",
"zh:417afb08d7b2744429f6b76806f4134d62b0354acf98e8a6c00de3c24f2bb6ad",
"zh:622165d09d21d9a922c86f1fc7177a400507f2a8c4a4513114407ae04da2dd29",
"zh:7cdb8e39a8ea0939558d87d2cb6caceded9e21f21003d9e9f9ce648d5db0bc3a",
"zh:851e737dc551d6004a860a8907fda65118fc2c7ede9fa828f7be704a2a39e68f",
"zh:a331ad289a02a2c4473572a573dc389be0a604cdd9e03dd8dbc10297fb14f14d",
"zh:b67fd531251380decd8dd1f849460d60f329f89df3d15f5815849a1dd001f430",
"zh:be8785957acca4f97aa3e800b313b57d1fca07788761c8867c9bc701fbe0bdb5",
"zh:cb6579a259fe020e1f88217d8f6937b2d5ace15b6406370977a1966eb31b1ca5",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}
provider "registry.terraform.io/hashicorp/google-beta" {
version = "4.85.0"
constraints = ">= 4.40.0, < 5.0.0"
hashes = [
"h1:YkCDGkP0AUZoNobLoxRnM52Pi4alYE9EFXalEu8p8E8=",
"zh:40e9c7ec46955b4d79065a14185043a4ad6af8d0246715853fc5c99208b66980",
"zh:5950a9ba2f96420ea5335b543e315b1a47a705f9a9abfc53c6fec52d084eddcb",
"zh:5dfa98d32246a5d97e018f2b91b0e921cc6f061bc8591884f3b144f0d62f1c20",
"zh:628d0ca35c6d4c35077859bb0a5534c1de44f23a91e190f9c3f06f2358172e75",
"zh:6e78d54fd4de4151968149b4c3521f563a8b5c55aad423dba5968a9114b65ae4",
"zh:91c3bc443188638353285bd35b06d3a3b39b42b3b4cc0637599a430438fba2f7",
"zh:9e91b03363ebf39eea5ec0fbe7675f6979883aa9ad9a36664357d8513a007cf3",
"zh:db9a8d6bfe075fb38c260986ab557d40e8d18e5698c62956a6da8120fae01d59",
"zh:e41169c49f3bb53217905509e2ba8bb4680c373e1f54db7fac1b7f72943a1004",
"zh:f32f55a8af605afbc940814e17493ac83d9d66cd6da9bbc247e0a833a0aa37ec",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
"zh:f6561a6badc3af842f9ad5bb926104954047f07cb90fadcca1357441cc67d91d",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.7.1"
constraints = ">= 2.1.0"
hashes = [
"h1:/qtweZW2sk0kBNiQM02RvBXmlVdI9oYqRMCyBZ8XA98=",
"zh:3193b89b43bf5805493e290374cdda5132578de6535f8009547c8b5d7a351585",
"zh:3218320de4be943e5812ed3de995946056db86eb8d03aa3f074e0c7316599bef",
"zh:419861805a37fa443e7d63b69fb3279926ccf98a79d256c422d5d82f0f387d1d",
"zh:4df9bd9d839b8fc11a3b8098a604b9b46e2235eb65ef15f4432bde0e175f9ca6",
"zh:5814be3f9c9cc39d2955d6f083bae793050d75c572e70ca11ccceb5517ced6b1",
"zh:63c6548a06de1231c8ee5570e42ca09c4b3db336578ded39b938f2156f06dd2e",
"zh:697e434c6bdee0502cc3deb098263b8dcd63948e8a96d61722811628dce2eba1",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:a0b8e44927e6327852bbfdc9d408d802569367f1e22a95bcdd7181b1c3b07601",
"zh:b7d3af018683ef22794eea9c218bc72d7c35a2b3ede9233b69653b3c782ee436",
"zh:d63b911d618a6fe446c65bfc21e793a7663e934b2fef833d42d3ccd38dd8d68d",
"zh:fa985cd0b11e6d651f47cff3055f0a9fd085ec190b6dbe99bf5448174434cdea",
]
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/time/README.md:
--------------------------------------------------------------------------------
```markdown
# time
src: https://github.com/dylibso/mcp.run-servlets/tree/main/servlets/time
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qr-code/README.md:
--------------------------------------------------------------------------------
```markdown
qr_code
=======
Source: [mcp.run-servlets](https://github.com/dylibso/mcp.run-servlets/tree/main/servlets/qr-code)
```
--------------------------------------------------------------------------------
/examples/plugins/v1/hash/README.md:
--------------------------------------------------------------------------------
```markdown
# hash
A hyper-mcp plugin that generates do all kind of hashes for you.
Supported hashing tools:
- [x] base64
- [x] base32
- [x] sha256
- [x] sha512
- [x] md5
- [x] sha1
- [x] sha224
- [x] sha384
## What it does
Takes input text and hash it.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fs/README.md:
--------------------------------------------------------------------------------
```markdown
# fs
An example plugin that implement filesystem operations.
## Usage
```json
{
"plugins": [
{
"name": "fs",
"path": "oci://ghcr.io/tuananh/fs-plugin:latest",
"runtime_config": {
"allowed_paths": ["/tmp"]
}
}
]
}
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/eval-py/README.md:
--------------------------------------------------------------------------------
```markdown
# eval_py
An example of using [RustPython](https://github.com/RustPython/RustPython) to evaluate Python code.

## Usage
```json
{
"plugins": [
{
"name": "eval_py",
"path": "/home/anh/Code/hyper-mcp/examples/plugins/v1/eval-py/target/wasm32-wasip1/release/plugin.wasm"
}
]
}
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/arxiv/README.md:
--------------------------------------------------------------------------------
```markdown
# arxiv
A plugin that let you search for papers on arXiv and download them.
## Usage
Call with:
```json
{
"plugins": [
// {},
{
"name": "arxiv",
"path": "/home/anh/Code/hyper-mcp/examples/plugins/v1/arxiv/target/wasm32-wasip1/release/plugin.wasm",
"runtime_config": {
"allowed_hosts": ["export.arxiv.org", "arxiv.org"],
"allowed_paths": ["/tmp"]
}
}
]
}
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crypto-price/README.md:
--------------------------------------------------------------------------------
```markdown
# crypto_price
## Usage
```json
{
"plugins": [
{
"name": "crypto_price",
"path": "oci://ghcr.io/tuananh/crypto-price-plugin:latest",
"runtime_config": {
"allowed_hosts": ["api.coingecko.com"]
}
}
]
}
```
## Notes
- HTTP request need to use `pdk.NewHTTPRequest`.
```go
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
resp := req.Send()
```
- We use `tinygo` for WASI support.
- Need to export `_Call` as `call` to make it consistent. Same with `describe`.
```
//export call
func _Call() int32 {
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/serper/README.md:
--------------------------------------------------------------------------------
```markdown
# serper
A plugin that performs Google web search using the [Serper](https://serper.dev) API and returns the raw JSON response for the given query string.
## Requirements
- Set `SERPER_API_KEY` in your config to your Serper API key.
## Usage
Call with:
```json
{
"plugins": [
{
"name": "serper",
"path": "oci://ghcr.io/tuananh/serper-plugin:latest",
"runtime_config": {
"env_vars": {
"SERPER_API_KEY": "<your-serper-api-key>"
},
"allowed_hosts": ["google.serper.dev"]
}
}
]
}
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fetch/README.md:
--------------------------------------------------------------------------------
```markdown
# fetch
src: https://github.com/dylibso/mcp.run-servlets/tree/main/servlets/fetch
A servlet that fetches web pages and converts them to markdown.
## What it does
Takes a URL, fetches the page content, strips out scripts and styles, and converts the HTML to markdown format.
## Usage
Call with:
```json
{
"plugins": [
// {},
{
"name": "fetch",
"path": "oci://ghcr.io/tuananh/fetch-plugin:latest",
"runtime_config": {
"allowed_hosts": ["*"]
}
}
]
}
```
Returns the page content converted to markdown format.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/github/README.md:
--------------------------------------------------------------------------------
```markdown
# github
[src](https://github.com/dylibso/mcp.run-servlets/tree/main/servlets/github)
You can interact with GitHub via various tools available in this plugin: branches, repo, gist, issues, files, etc...
## Usage
```json
{
"plugins": [
{
"name": "github",
"path": "oci://ghcr.io/tuananh/github-plugin:latest",
"runtime_config": {
"allowed_hosts": [
"api.github.com"
],
"env_vars": {
"api-key": "ghp_xxxx"
}
}
}
]
}
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/myip/README.md:
--------------------------------------------------------------------------------
```markdown
# myip
A example `hyper-mcp` plugin that tell you your IP address, using Cloudflare.
This is an example of how to use HTTP with `hyper-mcp`.
To use this, you will need to update your config like this. Note the `allowed_host` in `runtime_config` because we're using Cloudflare for this.
```json
{
"plugins": [
{
"name": "time",
"path": "/home/anh/Code/hyper-mcp/wasm/time.wasm"
},
{
"name": "qr_code",
"path": "oci://ghcr.io/tuananh/qrcode-plugin:latest"
},
{
"name": "hash",
"path": "oci://ghcr.io/tuananh/hash-plugin:latest"
},
{
"name": "myip",
"path": "oci://ghcr.io/tuananh/myip-plugin:latest",
"runtime_config": {
"allowed_hosts": ["1.1.1.1"]
}
}
]
}
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/sqlite/README.md:
--------------------------------------------------------------------------------
```markdown
# sqlite
A plugin that provide SQLite interactions for `hyper-mcp`.
## Usage
Call with:
```json
{
"plugins": [
// {},
{
"name": "sqlite",
"path": "oci://ghcr.io/tuananh/sqlite-plugin",
"runtime_config": {
"allowed_paths": ["/tmp"],
"env_vars": {
"db_path": "/tmp/memory.db"
}
}
}
]
}
```
## How to build
This plugin requires you to have [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) installed.
```sh
export WASI_SDK_PATH=`<wasi-sdk-path>` # in my case, it's /opt/wasi-sdk
export CC_wasm32_wasip1="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot"
cargo build --release --target wasm32-wasip1
```
See [Dockerfile](./Dockerfile) for reference.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/memory/README.md:
--------------------------------------------------------------------------------
```markdown
# memory
A plugin that let you save & retrieve memory, backed by SQLite.
## Usage
Call with:
```json
{
"plugins": [
// {},
{
"name": "memory",
"path": "/home/anh/Code/hyper-mcp/examples/plugins/v1/memory/target/wasm32-wasip1/release/plugin.wasm",
"runtime_config": {
"allowed_paths": ["/tmp"],
"env_vars": {
"db_path": "/tmp/memory.db"
}
}
}
]
}
```
## How to build
This plugin requires you to have [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) installed.
```sh
export WASI_SDK_PATH=`<wasi-sdk-path>` # in my case, it's /opt/wasi-sdk
export CC_wasm32_wasip1="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot"
cargo build --release --target wasm32-wasip1
```
See [Dockerfile](./Dockerfile) for reference.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/maven/README.md:
--------------------------------------------------------------------------------
```markdown
# maven
A plugin that fetches the dependencies of a Maven package (from Maven Central) given its group, artifact, and version.
## What it does
Given a Maven package (groupId, artifactId, version), fetches its POM file from Maven Central and returns its dependencies as JSON.
## Usage
Call with:
```json
{
"plugins": [
{
"name": "mvn_fetch_deps",
"path": "oci://ghcr.io/tuananh/maven-plugin:latest",
"runtime_config": {
"allowed_hosts": ["repo1.maven.org"]
}
}
]
}
```
### Example input
```json
{
"name": "mvn_fetch_deps",
"arguments": {
"group": "org.springframework.boot",
"artifact": "spring-boot-starter-web",
"version": "3.5.0"
}
}
```
### Example output
```json
{
"dependencies": [
{
"groupId": "org.springframework.boot",
"artifactId": "spring-boot-starter",
"version": "3.5.0",
"scope": "compile"
},
{
"groupId": "org.springframework.boot",
"artifactId": "spring-boot-starter-json",
"version": "3.5.0",
"scope": "compile"
}
// ...
]
}
```
Return the list of dependencies of the given Maven package.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/README.md:
--------------------------------------------------------------------------------
```markdown
# meme_generator
A plugin for generating memes using predefined templates with text overlays.
## What it does
Generates memes by overlaying customized text on predefined meme templates in classic meme style. The plugin supports various text styles, alignments, and positioning based on template configurations.
## Usage
Call with:
```json
{
"plugins": [
{
"name": "meme_generator",
"path": "oci://ghcr.io/tuananh/meme-generator-plugin:latest"
}
]
}
```
The plugin provides the following tools:
### meme_list_templates
Lists all available meme templates.
### meme_get_template
Gets details about a specific meme template.
Parameters:
- `template_id`: The ID of the template to retrieve
### meme_generate
Generates a meme using a template and custom text.
Parameters:
- `template_id`: The ID of the template to use
- `texts`: Array of text strings to place on the meme according to the template configuration
Each template can have specific configurations for:
- Text positioning and alignment
- Font scaling and style (uppercase/normal)
- Text color
- Multiple text overlays
The generated output is a PNG image with the text overlaid on the template according to the specified configuration.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gomodule/README.md:
--------------------------------------------------------------------------------
```markdown
# gomodule
A plugin that fetches Go module information and latest versions from `proxy.golang.org`.
## What it does
Provides two main functionalities:
1. `go_module_latest_version`: Fetches the latest version of multiple Go modules
2. `go_module_info`: Fetches detailed information about multiple Go modules
## Usage
Call with:
```json
{
"plugins": [
{
"name": "gomodule",
"path": "oci://ghcr.io/tuananh/gomodule-plugin:latest",
"runtime_config": {
"allowed_hosts": ["proxy.golang.org"]
}
}
]
}
```
### Example Usage
1. Get latest version of multiple Go modules:
```json
{
"name": "go_module_latest_version",
"params": {
"module_names": "github.com/spf13/cobra,github.com/gorilla/mux,github.com/gin-gonic/gin"
}
}
```
2. Get detailed information about multiple Go modules:
```json
{
"name": "go_module_info",
"params": {
"module_names": "github.com/spf13/cobra,github.com/gorilla/mux,github.com/gin-gonic/gin"
}
}
```
Returns:
- For `go_module_latest_version`: A JSON object mapping module names to their latest version numbers
- For `go_module_info`: An array of JSON objects containing detailed module information for each module, including:
- Name
- Latest version
- Time
- Version
- And other metadata from proxy.golang.org
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crates-io/README.md:
--------------------------------------------------------------------------------
```markdown
# crates-io
A plugin that fetches crate information and latest versions from crates.io.
## What it does
Provides two main functionalities:
1. `crates_io_latest_version`: Fetches the latest version of multiple crates
2. `crates_io_crate_info`: Fetches detailed information about multiple crates including description, downloads, repository, documentation, etc.
## Usage
Call with:
```json
{
"plugins": [
{
"name": "crates-io",
"path": "oci://ghcr.io/tuananh/crates-io-plugin:latest",
"runtime_config": {
"allowed_hosts": ["crates.io"]
}
}
]
}
```
### Example Usage
1. Get latest version of multiple crates:
```json
{
"name": "crates_io_latest_version",
"params": {
"crate_names": "serde,tokio,clap"
}
}
```
2. Get detailed information about multiple crates:
```json
{
"name": "crates_io_crate_info",
"params": {
"crate_names": "serde,tokio,clap"
}
}
```
Returns:
- For `crates_io_latest_version`: A JSON object mapping crate names to their latest version numbers
- For `crates_io_crate_info`: An array of JSON objects containing detailed crate information for each crate, including:
- Name
- Description
- Latest version
- Download count
- Repository URL
- Documentation URL
- Homepage URL
- Keywords
- Categories
- License
- Creation and update timestamps
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/README.md:
--------------------------------------------------------------------------------
```markdown
# qdrant
A plugin that provides vector similarity search capabilities using Qdrant vector database.
## What it does
This plugin provides three main functionalities:
1. Create collections with configurable vector dimensions
2. Store documents with their vector embeddings in Qdrant
3. Search for similar documents using vector embeddings
## Configuration
The plugin requires the following configuration:
```json
{
"plugins": [
{
"name": "qdrant",
"path": "oci://ghcr.io/tuananh/qdrant-plugin:latest",
"runtime_config": {
"QDRANT_URL": "http://localhost:6334",
"allowed_hosts": [
"localhost:6333"
],
"env_vars": {
"QDRANT_URL": "http://localhost:6333"
}
}
}
]
}
```
## Tools
### 1. qdrant_create_collection
Creates a new collection in Qdrant with specified vector dimensions.
```json
{
"collection_name": "my_documents",
"vector_size": 384 // Optional, defaults to 384
}
```
### 2. qdrant_store
Stores a document with its vector embedding in Qdrant.
```json
{
"collection_name": "my_documents",
"text": "Your document text",
"vector": [0.1, 0.2, ...] // Vector dimensions must match collection's vector_size
}
```
### 3. qdrant_find
Finds similar documents using vector similarity search.
```json
{
"collection_name": "my_documents",
"vector": [0.1, 0.2, ...], // Vector dimensions must match collection's vector_size
"limit": 5 // Optional, defaults to 5
}
```
## Features
- Configurable vector dimensions per collection
- Support for vector-based queries
- Configurable similarity search results limit
- Uses cosine similarity for vector matching
- Thread-safe operations
## Dependencies
- Qdrant for vector storage and similarity search
- UUID for document identification
```
--------------------------------------------------------------------------------
/examples/plugins/v1/think/README.md:
--------------------------------------------------------------------------------
```markdown
# think
A simple MCP plugin that returns the provided thought string. Useful for agentic reasoning, cache memory, or when you want to "think out loud" in a workflow.
## What it does
Takes a `thought` parameter (string) and simply returns it as the result. No side effects, no logging, no database or network calls.
Read more about the think tool in [this blog post](https://www.anthropic.com/engineering/claude-think-tool).
## Usage
Call with:
```json
{
"plugins": [
{
"name": "think",
"path": "oci://ghcr.io/tuananh/think-plugin:latest"
}
]
}
```
### Example
Tool call:
```json
{
"name": "think",
"arguments": { "thought": "I should try a different approach." }
}
```
Returns:
```json
"I should try a different approach."
```
## Example usage with Cursor/Windsurf
Add a new Cursor/Windsurf rule like the following
```
After any context change (viewing new files, running commands, or receiving tool outputs), use the "think" tool to organize your reasoning before responding.
Specifically, always use the think tool when:
- After examining file contents or project structure
- After running terminal commands or analyzing their outputs
- After receiving search results or API responses
- Before making code suggestions or explaining complex concepts
- When transitioning between different parts of a task
When using the think tool:
- List the specific rules or constraints that apply to the current task
- Check if all required information is collected
- Verify that your planned approach is correct
- Break down complex problems into clearly defined steps
- Analyze outputs from other tools thoroughly
- Plan multi-step approaches before executing them
The think tool has been proven to improve performance by up to 54% on complex tasks, especially when working with multiple tools or following detailed policies.
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/context7/README.md:
--------------------------------------------------------------------------------
```markdown
# Context7 API Tools Plugin
This plugin provides tools to interact with the Context7 API, allowing for resolving library IDs and fetching documentation.
## Usage
```
{
"plugins": [
{
"name": "context7",
"path": "oci://ghcr.io/tuananh/context7-plugin:nightly",
"runtime_config": {
"allowed_hosts": ["context7.com"]
}
}
]
}
```
## Tools
### 1. `c7_resolve_library_id`
**Description:** Resolves a package name to a Context7-compatible library ID and returns a list of matching libraries. You MUST call this function before 'c7_get_library_docs' to obtain a valid Context7-compatible library ID. When selecting the best match, consider: - Name similarity to the query - Description relevance - Code Snippet count (documentation coverage) - GitHub Stars (popularity) Return the selected library ID and explain your choice. If there are multiple good matches, mention this but proceed with the most relevant one.
**Input Schema:**
An object with the following properties:
- `library_name` (string, required): The general name of the library (e.g., 'React', 'upstash/redis').
**Example Input:**
```json
{
"library_name": "upstash/redis"
}
```
**Output:**
A JSON string containing the resolved Context7 compatible library ID.
**Example Output:**
```json
{
"context7_compatible_library_id": "upstash_redis_id"
}
```
### 2. `c7_get_library_docs`
**Description:** Fetches up-to-date documentation for a library. You must call 'c7_resolve_library_id' first to obtain the exact Context7-compatible library ID required to use this tool.
**Input Schema:**
An object with the following properties:
- `context7_compatible_library_id` (string, required): The Context7-compatible ID for the library.
- `topic` (string, optional): Focus the docs on a specific topic (e.g., 'routing', 'hooks').
- `tokens` (integer, optional): Max number of tokens for the documentation (default: 10000).
**Example Input:**
```json
{
"context7_compatible_library_id": "upstash_redis_id",
"topic": "data_types",
"tokens": 5000
}
```
**Output:**
The fetched documentation in text format.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gitlab/README.md:
--------------------------------------------------------------------------------
```markdown
# gitlab
A plugin that implements GitLab operations including issue management, file handling, branch management, and snippet operations.
## Configuration
The plugin requires the following configuration:
- `GITLAB_TOKEN`: (Required) Your GitLab personal access token
- `GITLAB_URL`: (Optional) Your GitLab instance URL. Defaults to `https://gitlab.com/api/v4`
## Usage
```json
{
"plugins": [
{
"name": "gitlab",
"path": "oci://ghcr.io/tuananh/gitlab-plugin:latest",
"runtime_config": {
"allowed_hosts": ["gitlab.com"], // Your GitLab host
"env_vars": {
"GITLAB_TOKEN": "your-gitlab-token",
"GITLAB_URL": "https://gitlab.com/api/v4" // Optional, defaults to GitLab.com
}
}
}
]
}
```
## Available Operations
### Issues
- [x] `gl_create_issue`: Create a new issue
- [x] `gl_get_issue`: Get issue details
- [x] `gl_update_issue`: Update an existing issue
- [x] `gl_add_issue_comment`: Add a comment to an issue
- [x] `gl_list_issues`: List issues for a project in GitLab. Supports filtering by state and labels.
### Files
- [x] `gl_get_file_contents`: Get file contents
- [x] `gl_create_or_update_file`: Create or update a file
- [x] `gl_delete_file`: Delete a file from the repository
- [ ] `gl_push_files`: Push multiple files
### Branches and Merge Requests
- [x] `gl_create_branch`: Create a new branch
- [x] `gl_list_branches`: List all branches in a GitLab project
- [x] `gl_create_merge_request`: Create a merge request
- [x] `gl_update_merge_request`: Update an existing merge request in a GitLab project.
- [x] `gl_get_merge_request`: Get details of a specific merge request in a GitLab project.
### Snippets
- [x] `gl_create_snippet`: Create a new snippet
- [x] `gl_update_snippet`: Update an existing snippet
- [x] `gl_get_snippet`: Get snippet details
- [x] `gl_delete_snippet`: Delete a snippet
### Repository
- [x] `gl_get_repo_tree`: Get the list of files and directories in a project repository. Handles pagination internally.
- [x] `gl_get_repo_members`: Get a list of members for a GitLab project. Supports fetching direct or inherited members and filtering by query. Handles pagination internally.
```
--------------------------------------------------------------------------------
/examples/plugins/v1/tool-list-changed/README.md:
--------------------------------------------------------------------------------
```markdown
# Tool List Changed Plugin
This plugin demonstrates dynamic tool list management in hyper-mcp. It showcases how a plugin can modify its tool list at runtime and notify the MCP server about these changes.
## Features
- **Dynamic Tool Creation**: Starts with a single `add_tool` and dynamically creates new tools
- **Host Function Integration**: Uses the `notify_tool_list_changed` host function to notify the server
- **Atomic Counter**: Thread-safe tool counting using atomic operations
## How It Works
The plugin begins with only one callable tool:
- `add_tool`: When called, this tool creates a new tool named `tool_n` (where n starts at 1 and increments)
After each call to `add_tool`:
1. A new tool `tool_n` is added to the plugin's tool list
2. The plugin calls `notify_tool_list_changed()` to inform the MCP server
3. The server updates its understanding of available tools
## Tools
### Initial Tool
- **add_tool**: Creates a new dynamic tool and notifies the server of the tool list change
- Takes no parameters
- Returns a JSON object with the new tool name and current tool count
### Dynamic Tools
- **tool_1, tool_2, tool_3, ...**: Created dynamically when `add_tool` is called
- Each tool returns information about itself when called
- Takes no parameters
## Usage Example
1. **Initial state**: Only `add_tool` is available
2. **Call `add_tool`**: Creates `tool_1` and notifies the server
3. **Call `add_tool` again**: Creates `tool_2` and notifies the server
4. **Call `tool_1`**: Returns information about being the first dynamically created tool
## Building
```bash
cd hyper-mcp/examples/plugins/v1/tool-list-changed
cargo build --target wasm32-unknown-unknown --release
```
The compiled WASM file will be available at:
`target/wasm32-unknown-unknown/release/tool_list_changed.wasm`
## Configuration
This plugin requires no additional configuration. It uses atomic operations to maintain thread-safe state across calls.
## Implementation Details
- Uses `AtomicUsize` for thread-safe tool counting
- Calls the `notify_tool_list_changed` host function after each tool addition
- Implements both static (`add_tool`) and dynamic (`tool_n`) tool handling
- Provides JSON responses with relevant information about operations
```
--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/README.md:
--------------------------------------------------------------------------------
```markdown
# rstime Plugin
A Model Context Protocol (MCP) plugin for working with time and timezone information. The `rstime` plugin provides tools for retrieving the current time in different timezones and parsing RFC2822 formatted time strings.
## Overview
The rstime plugin is a WebAssembly-based MCP plugin written in Rust that exposes time-related functionality through the Model Context Protocol. It allows LLM clients to:
- Get the current time in any timezone
- Parse RFC2822 formatted time strings to Unix timestamps
- Complete timezone names for better user experience
## Features
### Tools
#### `get_time`
Returns the current time in a specified timezone.
**Input:**
- `timezone` (optional, string): The timezone identifier (e.g., `America/New_York`, `Europe/London`, `Asia/Tokyo`). Defaults to `UTC` if not provided.
**Output:**
- `current_time` (string): The current time in RFC2822 format for the specified timezone.
**Example:**
```
Tool: get_time
Input: {"timezone": "America/Los_Angeles"}
Output: "Tue, 15 Jan 2024 10:30:45 -0800"
```
#### `parse_time`
Parses an RFC2822 formatted time string and returns the corresponding Unix timestamp.
**Input:**
- `time` (required, string): The time string in RFC2822 format to parse.
**Output:**
- `timestamp` (integer): The parsed timestamp in seconds since the Unix epoch.
**Example:**
```
Tool: parse_time
Input: {"time": "Tue, 15 Jan 2024 18:30:45 +0000"}
Output: 1705344645
```
### Prompts
#### `get_time_with_timezone`
A prompt that guides the user to get the current time for a specific timezone.
**Arguments:**
- `timezone` (optional, string): The timezone to retrieve time information for. Defaults to `UTC`.
**Response:**
Returns an assistant message suggesting to get the time for the specified timezone.
## Building
### Prerequisites
- Rust 1.70 or later
- Extism toolchain for WASM compilation
### Build Steps
```bash
# Build the WebAssembly plugin
cargo build --release --target wasm32-wasip1
# The compiled WASM file will be available at target/wasm32-wasip1/release/plugin.wasm
```
Alternatively, you can use the provided `prepare.sh` script:
```bash
./prepare.sh
```
This will create the compiled `rstime.wasm` file.
## Docker Support
A Dockerfile is included for containerized deployment. Build it with:
```bash
docker build -t rstime:latest .
```
## Testing
The plugin includes comprehensive test coverage. Run tests with, changing the target to your architecture:
```bash
cargo test --lib --target x86_64-apple-darwin
```
### Test Coverage
The test suite covers:
- Getting time in UTC and various timezones
- Handling invalid timezone names
- Parsing valid and invalid RFC2822 time strings
- Tool listing and metadata
- Prompt retrieval and listing
- Resource operations
- Error handling and edge cases
## Supported Timezones
The plugin supports all timezones from the IANA timezone database through the `chrono-tz` crate. Some common examples include:
- `UTC` - Coordinated Universal Time
- `America/New_York` - Eastern Time
- `America/Chicago` - Central Time
- `America/Denver` - Mountain Time
- `America/Los_Angeles` - Pacific Time
- `Europe/London` - Greenwich Mean Time
- `Europe/Paris` - Central European Time
- `Asia/Tokyo` - Japan Standard Time
- `Asia/Shanghai` - China Standard Time
- `Australia/Sydney` - Australian Eastern Time
For a complete list, refer to the IANA timezone database.
## Dependencies
- **chrono** - Date and time handling
- **chrono-tz** - Timezone support
- **extism-pdk** - Extism Plugin Development Kit for MCP
- **serde** - Serialization/deserialization
- **serde_json** - JSON handling
- **anyhow** - Error handling
- **base64** - Base64 encoding/decoding
## Usage Example
When integrated with an MCP-compatible client, you can use the plugin like this:
```
User: What time is it in Tokyo?
Client calls: get_time tool with {"timezone": "Asia/Tokyo"}
Plugin returns: Current time in RFC2822 format for Asia/Tokyo
Client: The current time in Tokyo is [result]
```
## Architecture
The plugin follows the Extism PDK architecture:
- **lib.rs**: Main plugin implementation with tool and prompt logic
- **pdk**: PDK-specific types and exported functions for MCP communication
- **Cargo.toml**: Rust dependencies and build configuration
The plugin compiles to WebAssembly and implements the MCP protocol through Extism's foreign function interface.
## Error Handling
The plugin gracefully handles common errors:
- **Invalid timezone**: Returns a descriptive error message
- **Missing required arguments**: Provides helpful feedback about required parameters
- **Invalid time format**: Reports parsing errors with context
- **Unknown tools/prompts**: Returns appropriate error responses
## Contributing
When contributing to this plugin:
1. Maintain the existing code style
2. Add tests for new functionality
3. Update this README with any new features
4. Ensure all tests pass before submitting
## License
This plugin is part of the hyper-mcp project. See the main project repository for license information.
```
--------------------------------------------------------------------------------
/templates/plugins/README.md:
--------------------------------------------------------------------------------
```markdown
# Plugin Templates
This directory contains templates for creating plugins for hyper-mcp in various programming languages. Plugins extend hyper-mcp's functionality by providing tools, resources, prompts, and other MCP capabilities through WebAssembly modules.
## Available Templates
### 🦀 Rust
The recommended language for building hyper-mcp plugins. Rust provides excellent performance, safety, and tooling for WebAssembly development.
- **Location**: `rust/`
- **Getting Started**: See [rust/README.md](./rust/README.md)
- **Use When**: You want the best performance, safety, and ecosystem support
- **Compile Target**: WebAssembly (`wasm32-wasip1`)
**Key Features:**
- Excellent WASM performance and code size
- Strong type system catches errors at compile time
- Rich ecosystem of crates for common tasks
- Memory-safe execution model
- Direct support for Extism PDK
### 🐹 Go
A modern, approachable language for building hyper-mcp plugins. Go offers simplicity, fast compilation, and a clean standard library for WebAssembly development.
- **Location**: `go/`
- **Getting Started**: See [go/README.md](./go/README.md)
- **Use When**: You prefer simplicity, fast development cycles, and Go's syntax
- **Compile Target**: WebAssembly (`wasip1`)
**Key Features:**
- Simple, readable syntax with fast learning curve
- Fast compilation times
- Excellent standard library
- Strong concurrency primitives (though limited in WASM)
- Growing WASM ecosystem with Extism Go PDK support
## Quick Start
1. **Choose a template language** (Rust or Go)
2. **Read the template README** for language-specific setup instructions
3. **Implement your plugin** by adding tools, resources, prompts, etc.
4. **Build and test locally** using the provided build instructions
5. **Publish to a registry** following the distribution guide
## Plugin Capabilities
Plugins can provide any combination of:
- **Tools** - Functions that clients can call with structured inputs
- **Resources** - URI-based references to files, data, or services
- **Resource Templates** - URI patterns for dynamic resource discovery
- **Prompts** - Pre-defined prompts for specific use cases
- **Completions** - Auto-completion suggestions for user input
## Plugin Development Workflow
```
1. Create project from template
↓
2. Implement plugin handlers
↓
3. Build to WebAssembly
↓
4. Test locally with hyper-mcp
↓
5. Build Docker image
↓
6. Push to registry (Docker Hub, GHCR, etc.)
↓
7. Configure in hyper-mcp's config.json
↓
8. Use in Claude Desktop, Cursor IDE, or other MCP clients
```
## Common Tasks
### Set Up Development Environment
Follow the language-specific template README (e.g., [rust/README.md](./rust/README.md) or [go/README.md](./go/README.md)) for:
- Required tools and dependencies
- Target/runtime setup
- Local build instructions
### Implement Plugin Handlers
**Only implement what you need** - for example:
- Tools-only plugin: `list_tools()` + `call_tool()`
- Resources-only plugin: `list_resources()` + `read_resource()`
- Prompts-only plugin: `list_prompts()` + `get_prompt()`
See the template README for a complete handler reference table.
### Call Host Functions
Your plugin can call host functions to interact with the MCP client:
- Request user input with `create_elicitation()`
- Generate messages with `create_message()`
- Report progress with `notify_progress()`
- Send logs with `notify_logging_message()`
- Query available roots with `list_roots()`
- Notify about changes to tools, resources, or prompts
See the template README for complete host function documentation.
### Build for Production
Each template includes a `Dockerfile` for reproducible, multi-stage builds:
```bash
docker build -t your-registry/your-plugin-name .
docker push your-registry/your-plugin-name:latest
```
### Configure in hyper-mcp
Add your plugin to hyper-mcp's config file:
```json
{
"plugins": {
"my_plugin": {
"url": "oci://your-registry/your-plugin-name:latest"
}
}
}
```
For local development, use a file:// URL:
```json
{
"plugins": {
"my_plugin": {
"url": "file:///path/to/plugin.wasm"
}
}
}
```
## Language Comparison
| Feature | Rust | Go |
|---------|------|-----|
| Performance | Excellent | Very Good |
| Code Size | Small | Medium |
| Learning Curve | Steep | Gentle |
| Compilation Speed | Moderate | Fast |
| Type Safety | Very Strong | Good |
| Standard Library | Moderate | Excellent |
| WASM Support | Native | Excellent (1.22+) |
| Extism Support | Full | Full |
## Resources
- [hyper-mcp Main README](https://github.com/tuananh/hyper-mcp#readme)
- [hyper-mcp Plugin Creation Guide](https://github.com/tuananh/hyper-mcp/blob/main/CREATING_PLUGINS.md)
- [MCP Protocol Specification](https://spec.modelcontextprotocol.io/)
- [Extism Documentation](https://docs.extism.org/)
- [Extism Go PDK](https://github.com/extism/go-pdk)
- [Example Plugins](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins)
## Adding More Templates
To add a template for another language:
1. Create a new directory: `templates/plugins/your-language/`
2. Set up the build system for compiling to WebAssembly
3. Create a `Dockerfile` for building OCI container images
4. Add a comprehensive `README.md` following the pattern from existing templates
5. Include example implementations of key handlers
6. Convert all MCP protocol types from the Rust `types.rs` to your language
7. Submit as a contribution to hyper-mcp
## Support
- Check the template README for language-specific questions
- See [CREATING_PLUGINS.md](../CREATING_PLUGINS.md) for general plugin development
- Review [example plugins](../examples/plugins/) for working implementations
- Open an issue on [GitHub](https://github.com/tuananh/hyper-mcp) for bugs or feature requests
Happy plugin building! 🚀
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
<div align="center">
<picture>
<img alt="hyper-mcp logo" src="./assets/logo.png" width="50%">
</picture>
</div>
<div align="center">
[](https://crates.io/crates/hyper-mcp)
[](#license)
[](https://github.com/tuananh/hyper-mcp/issues)

<a href="https://trendshift.io/repositories/13451" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13451" alt="tuananh%2Fhyper-mcp | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>
# hyper-mcp
A fast, secure MCP server that extends its capabilities through WebAssembly plugins.
## What is it?
hyper-mcp makes it easy to add AI capabilities to your applications. It works with Claude Desktop, Cursor IDE, and other MCP-compatible apps. Write plugins in your favorite language, distribute them through container registries, and run them anywhere - from cloud to edge.
## Features
- Write plugins in any language that compiles to WebAssembly
- Distribute plugins via standard OCI registries (like Docker Hub)
- Built on [Extism](https://github.com/extism/extism) for rock-solid plugin support
- Sanboxing with WASM: ability to limit network, filesystem, memory access
- Lightweight enough for resource-constrained environments
- Support all 3 protocols in the spec: `stdio`, `sse` and `streamble-http`.
- Deploy anywhere: serverless, edge, mobile, IoT devices
- Cross-platform compatibility out of the box
- Support tool name prefix to prevent tool names collision
## Security
Built with security-first mindset:
- Sandboxed plugins that can't access your system without permission
- Memory-safe execution with resource limits
- Secure plugin distribution through container registries
- Fine-grained access control for host functions
- OCI plugin images are signed at publish time and verified at load time with [sigstore](https://www.sigstore.dev/).
## Getting Started
1. Create your config file:
- Linux: `$HOME/.config/hyper-mcp/config.json`
- Windows: `{FOLDERID_RoamingAppData}`. Eg: `C:\Users\Alice\AppData\Roaming`
- macOS: `$HOME/Library/Application Support/hyper-mcp/config.json`
```json
{
"plugins": {
"time": {
"url": "oci://ghcr.io/tuananh/time-plugin:latest"
},
"qr_code": {
"url": "oci://ghcr.io/tuananh/qrcode-plugin:latest"
},
"hash": {
"url": "oci://ghcr.io/tuananh/hash-plugin:latest"
},
"myip": {
"url": "oci://ghcr.io/tuananh/myip-plugin:latest",
"runtime_config": {
"allowed_hosts": ["1.1.1.1"]
}
},
"fetch": {
"url": "oci://ghcr.io/tuananh/fetch-plugin:latest",
"runtime_config": {
"allowed_hosts": ["*"],
"memory_limit": "100 MB",
}
}
}
}
```
> 📖 **For detailed configuration options including authentication setup, runtime configuration, and advanced features, see [RUNTIME_CONFIG.md](./RUNTIME_CONFIG.md)**
Supported URL schemes:
- `oci://` - for OCI-compliant registries (like Docker Hub, GitHub Container Registry, etc.)
- `file://` - for local files
- `http://` or `https://` - for remote files
- `s3://` - for Amazon S3 objects (requires that you have your AWS credentials set up in the environment)
2. Start the server:
```sh
$ hyper-mcp
```
- By default, it will use `stdio` transport. If you want to use SSE, use flag `--transport sse` or streamable HTTP with `--transport streamable-http`.
- If you want to debug, use `RUST_LOG=info`.
- If you're loading unsigned OCI plugin, you need to set `insecure_skip_signature` flag or env var `HYPER_MCP_INSECURE_SKIP_SIGNATURE` to `true`
## Using with Cursor IDE
You can configure hyper-mcp either globally for all projects or specifically for individual projects.
1. For project-scope configuration, create `.cursor/mcp.json` in your project root:
```json
{
"mcpServers": {
"hyper-mcp": {
"command": "/path/to/hyper-mcp"
}
}
}
```
2. Set up hyper-mcp in Cursor's settings:

3. Start using tools through chat:

## Available Plugins
We maintain several example plugins to get you started:
### V1 Plugins
These plugins use the v1 plugin interface. While still supported, new plugins should use the v2 interface.
- [time](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/time): Get current time and do time calculations (Rust)
- [qr_code](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/qr-code): Generate QR codes (Rust)
- [hash](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/hash): Generate various types of hashes (Rust)
- [myip](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/myip): Get your current IP (Rust)
- [fetch](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/fetch): Basic webpage fetching (Rust)
- [crypto_price](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/crypto-price): Get cryptocurrency prices (Go)
- [fs](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/fs): File system operations (Rust)
- [github](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/github): GitHub plugin (Go)
- [eval_py](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/eval-py): Evaluate Python code with RustPython (Rust)
- [arxiv](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/arxiv): Search & download arXiv papers (Rust)
- [memory](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/memory): Let you store & retrieve memory, powered by SQLite (Rust)
- [sqlite](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/sqlite): Interact with SQLite (Rust)
- [crates-io](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/crates-io): Get crate general information, check crate latest version (Rust)
- [gomodule](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/gomodule): Get Go modules info, version (Rust)
- [qdrant](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/qdrant): keeping & retrieving memories to Qdrant vector search engine (Rust)
- [gitlab](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/gitlab): GitLab plugin (Rust)
- [meme_generator](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/meme-generator): Meme generator (Rust)
- [context7](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/context7): Lookup library documentation (Rust)
- [think](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/think): Think tool(Rust)
- [maven](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/maven): Maven plugin (Rust)
- [serper](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v1/serper): Serper web search plugin (Rust)
### V2 Plugins
These plugins use the v2 plugin interface. New plugins should use this interface.
- [rstime](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v2/rstime): Get current time and do time calculations (Rust)
### Community-built plugins
- [hackernews](https://github.com/hungran/hyper-mcp-hackernews-tool): This plugin connects to the Hacker News API to fetch the current top stories and display them with their titles, scores, authors, and URLs.
- [release-monitor-id](https://github.com/ntheanh201/hyper-mcp-release-monitor-id-tool): This plugin retrieves project ID from release-monitoring.org, which helps track versions of released software.
- [yahoo-finance](https://github.com/phamngocquy/hyper-mcp-yfinance): This plugin connects to the Yahoo Finance API to provide stock prices (OHLCV) based on a company name or ticker symbol.
- [rand16](https://github.com/dabevlohn/rand16): This plugen generates random 16 bytes buffer and provides it in base64uri format - very usable for symmetric cryptography online.
## Documentation
- **[Runtime Configuration Guide](./RUNTIME_CONFIG.md)** - Comprehensive guide to configuration options including:
- Authentication setup (Basic, Token, and Keyring)
- Plugin runtime configuration
- Security considerations and best practices
- Platform-specific keyring setup for macOS, Linux, and Windows
- Troubleshooting authentication issues
- **[Skip Tools Pattern Guide](./SKIP_TOOLS_GUIDE.md)** - Comprehensive guide to filtering tools using regex patterns:
- Pattern syntax and examples
- Common use cases and best practices
- Environment-specific filtering strategies
- Advanced regex techniques
- Migration and troubleshooting
## Creating Plugins
For comprehensive instructions on creating plugins, see [CREATING_PLUGINS.md](./CREATING_PLUGINS.md).
## License
[Apache 2.0](./LICENSE)
## Star History
[](https://www.star-history.com/#tuananh/hyper-mcp&Date)
```
--------------------------------------------------------------------------------
/templates/plugins/rust/README.md:
--------------------------------------------------------------------------------
```markdown
# Rust Plugin Template
A WebAssembly plugin template for building MCP (Model Context Protocol) plugins in Rust using the hyper-mcp framework.
## Overview
This template provides a starter project for creating MCP plugins that run as WebAssembly modules. It includes all necessary dependencies and boilerplate code to implement MCP protocol handlers.
## Project Structure
```
.
├── src/
│ ├── lib.rs # Main plugin implementation
│ └── pdk/ # Plugin Development Kit types and utilities
├── Cargo.toml # Rust dependencies and project metadata
├── Dockerfile # Multi-stage build for compiling to WASM
└── .cargo/ # Cargo configuration
```
## Getting Started
### Prerequisites
- Rust 1.88 or later
- `wasm32-wasip1` target installed:
```sh
rustup target add wasm32-wasip1
```
### Development
1. **Clone or use this template** to start your plugin project
2. **Implement plugin handlers** in `src/lib.rs`:
> **Note:** You only need to implement the handlers relevant to your plugin. For example, if your plugin only provides tools, implement only `list_tools()` and `call_tool()`. All other handlers have default implementations that work out of the box.
- `list_tools()` - Describe available tools
- `call_tool()` - Execute a tool
- `list_resources()` - List available resources
- `read_resource()` - Read resource contents
- `list_prompts()` - List available prompts
- `get_prompt()` - Get prompt details
- `complete()` - Provide auto-completion suggestions
3. **Build locally** (requires WASM target):
```sh
cargo build --release --target wasm32-wasip1
```
The compiled WASM module will be at: `target/wasm32-wasip1/release/plugin.wasm`
### Dependencies
The template includes key dependencies:
- **extism-pdk** - Plugin Development Kit for Extism
- **serde/serde_json** - JSON serialization/deserialization
- **anyhow** - Error handling
- **base64** - Base64 encoding/decoding
- **chrono** - Date/time handling
## Plugin Handler Functions
Your plugin can implement any combination of the following handlers. **Only implement the handlers your plugin needs** - the template provides sensible defaults for everything else:
| Handler | Purpose | Required For |
|---------|---------|--------------|
| `list_tools()` | Declare available tools | Tool-providing plugins |
| `call_tool()` | Execute a tool | Tool-providing plugins |
| `list_resources()` | Declare available resources | Resource-providing plugins |
| `list_resource_templates()` | Declare resource templates | Dynamic resource plugins |
| `read_resource()` | Read resource contents | Resource-providing plugins |
| `list_prompts()` | Declare available prompts | Prompt-providing plugins |
| `get_prompt()` | Retrieve a specific prompt | Prompt-providing plugins |
| `complete()` | Provide auto-completions | Plugins supporting completions |
| `on_roots_list_changed()` | Handle root changes | Plugins reacting to root changes |
**Example: Tools-only plugin**
If your plugin only provides tools, you only need to implement:
```rust
pub(crate) fn list_tools(_input: ListToolsRequest) -> Result<ListToolsResult> {
// Return your tools
}
pub(crate) fn call_tool(input: CallToolRequest) -> Result<CallToolResult> {
// Execute the requested tool
}
```
All other handlers will use their default implementations.
## Host Functions
Your plugin can call these host functions to interact with the client and MCP server. Import them from the `pdk` module:
```rust
use crate::pdk::imports::*;
```
### User Interaction
**`create_elicitation(input: ElicitRequestParamWithTimeout) -> Result<ElicitResult>`**
Request user input through the client's elicitation interface. Use this when your plugin needs user guidance, decisions, or confirmations during execution.
```rust
let result = create_elicitation(ElicitRequestParamWithTimeout {
request: ElicitRequestParam {
// Define what input you're requesting
..Default::default()
},
timeout_ms: Some(30000), // 30 second timeout
})?;
```
### Message Generation
**`create_message(input: CreateMessageRequestParam) -> Result<CreateMessageResult>`**
Request message creation through the client's sampling interface. Use this when your plugin needs intelligent text generation or analysis with AI assistance.
```rust
let result = create_message(CreateMessageRequestParam {
messages: vec![/* conversation history */],
model_preferences: Some(/* model preferences */),
system: Some("You are a helpful assistant".to_string()),
..Default::default()
})?;
```
### Resource Discovery
**`list_roots() -> Result<ListRootsResult>`**
List the client's root directories or resources. Use this to discover what root resources (typically file system roots) are available and understand the scope of resources your plugin can access.
```rust
let roots = list_roots()?;
for root in roots.roots {
println!("Root: {} at {}", root.name, root.uri);
}
```
### Logging
**`notify_logging_message(input: LoggingMessageNotificationParam) -> Result<()>`**
Send diagnostic, informational, warning, or error messages to the client. The client's logging level determines which messages are processed and displayed.
```rust
notify_logging_message(LoggingMessageNotificationParam {
level: "info".to_string(),
logger: Some("my_plugin".to_string()),
data: serde_json::json!({"message": "Processing started"}),
})?;
```
### Progress Reporting
**`notify_progress(input: ProgressNotificationParam) -> Result<()>`**
Report progress during long-running operations. Allows clients to display progress bars or status information to users.
```rust
notify_progress(ProgressNotificationParam {
progress: 50,
total: Some(100),
})?;
```
### List Change Notifications
Notify the client when your plugin's available items change:
**`notify_tool_list_changed() -> Result<()>`**
- Call this when you add, remove, or modify available tools
**`notify_resource_list_changed() -> Result<()>`**
- Call this when you add, remove, or modify available resources
**`notify_prompt_list_changed() -> Result<()>`**
- Call this when you add, remove, or modify available prompts
**`notify_resource_updated(input: ResourceUpdatedNotificationParam) -> Result<()>`**
- Call this when you modify the contents of a specific resource
```rust
// When your plugin's tools change
notify_tool_list_changed()?;
// When a specific resource is updated
notify_resource_updated(ResourceUpdatedNotificationParam {
uri: "resource://my-resource".to_string(),
})?;
```
### Example: Interactive Tool with Progress
```rust
pub(crate) fn call_tool(input: CallToolRequest) -> Result<CallToolResult> {
match input.name.as_str() {
"long_task" => {
// Log start
notify_logging_message(LoggingMessageNotificationParam {
level: "info".to_string(),
data: serde_json::json!({"message": "Starting long task"}),
..Default::default()
})?;
// Do work with progress updates
for i in 0..10 {
// ... do work ...
notify_progress(ProgressNotificationParam {
progress: (i + 1) * 10,
total: Some(100),
})?;
}
Ok(CallToolResult {
content: vec![Content {
type_: "text".to_string(),
text: Some("Task completed".to_string()),
..Default::default()
}],
..Default::default()
})
},
_ => Err(anyhow!("Unknown tool")),
}
}
```
## Building for Distribution
### Using Docker
The included `Dockerfile` provides a multi-stage build that compiles your plugin to WebAssembly:
```sh
docker build -t your-registry/your-plugin-name .
docker push your-registry/your-plugin-name
```
The Docker build:
1. Compiles your Rust code to `wasm32-wasip1` target
2. Creates a minimal image containing only the compiled `plugin.wasm`
3. Outputs an OCI-compatible container image
### Manual Build
To build manually without Docker:
```sh
# Install dependencies
rustup target add wasm32-wasip1
cargo install cargo-auditable
# Build
cargo auditable build --release --target wasm32-wasip1
# Result is at: target/wasm32-wasip1/release/plugin.wasm
```
## Implementation Guide
### Creating a Tool
Here's an example of implementing a simple tool:
```rust
pub(crate) fn list_tools(_input: ListToolsRequest) -> Result<ListToolsResult> {
Ok(ListToolsResult {
tools: vec![
Tool {
name: "greet".to_string(),
description: Some("Greet a person".to_string()),
input_schema: json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The person's name"
}
},
"required": ["name"]
}),
},
],
..Default::default()
})
}
pub(crate) fn call_tool(input: CallToolRequest) -> Result<CallToolResult> {
match input.name.as_str() {
"greet" => {
let name = input.arguments
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("name argument required"))?;
Ok(CallToolResult {
content: vec![Content {
type_: "text".to_string(),
text: Some(format!("Hello, {}!", name)),
..Default::default()
}],
..Default::default()
})
},
_ => Err(anyhow!("Unknown tool: {}", input.name)),
}
}
```
### Creating a Resource
Example of implementing a resource:
```rust
pub(crate) fn list_resources(_input: ListResourcesRequest) -> Result<ListResourcesResult> {
Ok(ListResourcesResult {
resources: vec![
ResourceDescription {
uri: "resource://example".to_string(),
name: Some("Example Resource".to_string()),
description: Some("An example resource".to_string()),
mime_type: Some("text/plain".to_string()),
},
],
..Default::default()
})
}
pub(crate) fn read_resource(input: ReadResourceRequest) -> Result<ReadResourceResult> {
match input.uri.as_str() {
"resource://example" => Ok(ReadResourceResult {
contents: vec![ResourceContents {
mime_type: Some("text/plain".to_string()),
text: Some("Resource content here".to_string()),
..Default::default()
}],
}),
_ => Err(anyhow!("Unknown resource: {}", input.uri)),
}
}
```
## Configuration in hyper-mcp
After building and publishing your plugin, configure it in hyper-mcp:
```json
{
"plugins": {
"my_plugin": {
"url": "oci://your-registry/your-plugin-name:latest"
}
}
}
```
For local development/testing:
```json
{
"plugins": {
"my_plugin": {
"url": "file:///path/to/target/wasm32-wasip1/release/plugin.wasm"
}
}
}
```
## Testing
To test your plugin locally:
1. Build it: `cargo build --release --target wasm32-wasip1`
2. Update hyper-mcp's config to point to `file://` URL
3. Start hyper-mcp with `RUST_LOG=debug`
4. Test through Claude Desktop, Cursor IDE, or another MCP client
## Resources
- [hyper-mcp Documentation](https://github.com/tuananh/hyper-mcp)
- [MCP Protocol Specification](https://spec.modelcontextprotocol.io/)
- [Extism Plugin Development Kit](https://docs.extism.org/docs/pdk)
- [Example Plugins](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins)
## License
Same as hyper-mcp - Apache 2.0
```
--------------------------------------------------------------------------------
/templates/plugins/go/README.md:
--------------------------------------------------------------------------------
```markdown
# Go Plugin Template
A WebAssembly plugin template for building MCP (Model Context Protocol) plugins in Go using the hyper-mcp framework.
## Overview
This template provides a starter project for creating MCP plugins that run as WebAssembly modules. It includes all necessary dependencies and boilerplate code to implement MCP protocol handlers.
## Project Structure
```
.
├── main.go # Plugin handler implementations
├── exports.go # WASM export wrappers for handlers
├── imports.go # Host function calls
├── types.go # MCP protocol types
├── go.mod # Go module definition
├── go.sum # Go module checksums
├── Dockerfile # Multi-stage build for compiling to WASM
└── .gitignore # Git ignore rules
```
## Getting Started
### Prerequisites
- Go 1.22 or later
- Docker (for building WASM)
- `clang` and `lld` (for WASM compilation)
### Development
1. **Clone or use this template** to start your plugin project
2. **Implement plugin handlers** in `main.go`:
Plugin handlers must be implemented without the use of goroutines *unless* you modify the Dockerfile build to remove `-scheduler=none` from the tinygo build flags. Note that this is not recommended, as hyper-mcp will normally handle concurrent executions for you.
> **Note:** You only need to implement the handlers relevant to your plugin. For example, if your plugin only provides tools, implement only `ListTools()` and `CallTool()`. All other handlers have default implementations that work out of the box.
- `ListTools()` - Describe available tools
- `CallTool()` - Execute a tool
- `ListResources()` - List available resources
- `ReadResource()` - Read resource contents
- `ListPrompts()` - List available prompts
- `GetPrompt()` - Get prompt details
- `Complete()` - Provide auto-completion suggestions
- `ListResourceTemplates()` - List resource templates
- `OnRootsListChanged()` - Handle root changes
3. **Build locally** (requires Docker for WASM target):
```sh
docker build -t your-plugin-name .
docker run --rm -v $(pwd):/workspace your-plugin-name cp /plugin.wasm /workspace/
```
### Dependencies
The template uses:
- **extism/go-pdk** - Plugin Development Kit for Extism
- Standard Go libraries for JSON serialization and time handling
## Plugin Handler Functions
Your plugin can implement any combination of the following handlers. **Only implement the handlers your plugin needs** - the template provides sensible defaults for everything else:
| Handler | Purpose | Required For |
|---------|---------|--------------|
| `ListTools()` | Declare available tools | Tool-providing plugins |
| `CallTool()` | Execute a tool | Tool-providing plugins |
| `ListResources()` | Declare available resources | Resource-providing plugins |
| `ListResourceTemplates()` | Declare resource templates | Dynamic resource plugins |
| `ReadResource()` | Read resource contents | Resource-providing plugins |
| `ListPrompts()` | Declare available prompts | Prompt-providing plugins |
| `GetPrompt()` | Retrieve a specific prompt | Prompt-providing plugins |
| `Complete()` | Provide auto-completions | Plugins supporting completions |
| `OnRootsListChanged()` | Handle root changes | Plugins reacting to root changes |
**Example: Tools-only plugin**
If your plugin only provides tools, you only need to implement:
```go
func ListTools(input ListToolsRequest) (*ListToolsResult, error) {
return &ListToolsResult{
Tools: []Tool{
{
Name: "greet",
Description: ptrString("Greet a person"),
InputSchema: ToolSchema{
Type: "object",
Properties: map[string]interface{}{
"name": map[string]interface{}{
"type": "string",
"description": "The person's name",
},
},
Required: []string{"name"},
},
},
},
}, nil
}
func CallTool(input CallToolRequest) (*CallToolResult, error) {
switch input.Request.Name {
case "greet":
name, ok := input.Request.Arguments["name"].(string)
if !ok {
return &CallToolResult{
Content: []json.RawMessage{
[]byte(`{"type":"text","text":"name argument required"}`),
},
}, nil
}
return &CallToolResult{
Content: []json.RawMessage{
[]byte(fmt.Sprintf(`{"type":"text","text":"Hello, %s!"}`, name)),
},
}, nil
default:
return &CallToolResult{
Content: []json.RawMessage{
[]byte(fmt.Sprintf(`{"type":"text","text":"Unknown tool: %s"}`, input.Request.Name)),
},
}, nil
}
}
```
All other handlers will use their default implementations.
## Host Functions
Your plugin can call these host functions to interact with the client and MCP server. Available through direct function calls in `imports.go`:
```go
// Example usage
result, err := CreateElicitation(ElicitRequestParamWithTimeout{...})
```
### User Interaction
**`CreateElicitation(input ElicitRequestParamWithTimeout) (*ElicitResult, error)`**
Request user input through the client's elicitation interface. Use this when your plugin needs user guidance, decisions, or confirmations during execution.
```go
result, err := CreateElicitation(ElicitRequestParamWithTimeout{
Message: "Please provide your name",
RequestedSchema: Schema{
Type: "object",
Properties: map[string]json.RawMessage{
"name": json.RawMessage(`{"type":"string"}`),
},
},
Timeout: ptrInt64(30000), // 30 second timeout
})
```
### Message Generation
**`CreateMessage(input CreateMessageRequestParam) (*CreateMessageResult, error)`**
Request message creation through the client's sampling interface. Use this when your plugin needs intelligent text generation or analysis with AI assistance.
```go
result, err := CreateMessage(CreateMessageRequestParam{
MaxTokens: 1024,
Messages: []json.RawMessage{
// conversation history
},
SystemPrompt: ptrString("You are a helpful assistant"),
})
```
### Resource Discovery
**`ListRoots() (*ListRootsResult, error)`**
List the client's root directories or resources. Use this to discover what root resources (typically file system roots) are available and understand the scope of resources your plugin can access.
```go
roots, err := ListRoots()
if err == nil {
for _, root := range roots.Roots {
fmt.Printf("Root: %s at %s\n", *root.Name, root.URI)
}
}
```
### Logging
**`NotifyLoggingMessage(input LoggingMessageNotificationParam) error`**
Send diagnostic, informational, warning, or error messages to the client. The client's logging level determines which messages are processed and displayed.
```go
NotifyLoggingMessage(LoggingMessageNotificationParam{
Level: LoggingLevelInfo,
Logger: ptrString("my_plugin"),
Data: json.RawMessage(`{"message": "Processing started"}`),
})
```
### Progress Reporting
**`NotifyProgress(input ProgressNotificationParam) error`**
Report progress during long-running operations. Allows clients to display progress bars or status information to users.
```go
NotifyProgress(ProgressNotificationParam{
Progress: 50,
ProgressToken: "task-1",
Total: ptrFloat64(100),
})
```
### List Change Notifications
Notify the client when your plugin's available items change:
**`NotifyToolListChanged() error`**
- Call this when you add, remove, or modify available tools
**`NotifyResourceListChanged() error`**
- Call this when you add, remove, or modify available resources
**`NotifyPromptListChanged() error`**
- Call this when you add, remove, or modify available prompts
**`NotifyResourceUpdated(input ResourceUpdatedNotificationParam) error`**
- Call this when you modify the contents of a specific resource
```go
// When your plugin's tools change
NotifyToolListChanged()
// When a specific resource is updated
NotifyResourceUpdated(ResourceUpdatedNotificationParam{
URI: "resource://my-resource",
})
```
### Example: Interactive Tool with Progress
```go
func CallTool(input CallToolRequest) (*CallToolResult, error) {
switch input.Request.Name {
case "long_task":
// Log start
NotifyLoggingMessage(LoggingMessageNotificationParam{
Level: LoggingLevelInfo,
Data: json.RawMessage(`{"message": "Starting long task"}`),
})
// Do work with progress updates
for i := 0; i < 10; i++ {
// ... do work ...
NotifyProgress(ProgressNotificationParam{
Progress: float64((i + 1) * 10),
ProgressToken: "task-1",
Total: ptrFloat64(100),
})
}
return &CallToolResult{
Content: []json.RawMessage{
[]byte(`{"type":"text","text":"Task completed"}`),
},
}, nil
default:
return &CallToolResult{
Content: []json.RawMessage{
[]byte(fmt.Sprintf(`{"type":"text","text":"Unknown tool: %s"}`, input.Request.Name)),
},
}, nil
}
}
```
## Building for Distribution
### Using Docker
The included `Dockerfile` provides a multi-stage build that compiles your plugin to WebAssembly:
```sh
docker build -t your-registry/your-plugin-name .
docker run --rm -v $(pwd):/workspace your-registry/your-plugin-name cp /plugin.wasm /workspace/
```
The Docker build:
1. Compiles your Go code to `wasip1` target
2. Creates a minimal image containing only the compiled `plugin.wasm`
3. Outputs an OCI-compatible container image
### Manual Build
To build manually without Docker (requires Go 1.22+):
```sh
# Build for WASM
GOOS=wasip1 GOARCH=wasm CGO_ENABLED=0 go build -o plugin.wasm ./
# Result is at: plugin.wasm
```
## Implementation Guide
### Creating a Tool
Here's an example of implementing a simple tool:
```go
func ListTools(input ListToolsRequest) (*ListToolsResult, error) {
return &ListToolsResult{
Tools: []Tool{
{
Name: "greet",
Description: ptrString("Greet a person"),
InputSchema: ToolSchema{
Type: "object",
Properties: map[string]interface{}{
"name": map[string]interface{}{
"type": "string",
"description": "The person's name",
},
},
Required: []string{"name"},
},
},
},
}, nil
}
func CallTool(input CallToolRequest) (*CallToolResult, error) {
switch input.Request.Name {
case "greet":
name, ok := input.Request.Arguments["name"].(string)
if !ok {
return &CallToolResult{
Content: []json.RawMessage{
[]byte(`{"type":"text","text":"name argument required"}`),
},
}, nil
}
return &CallToolResult{
Content: []json.RawMessage{
[]byte(fmt.Sprintf(`{"type":"text","text":"Hello, %s!"}`, name)),
},
}, nil
default:
return &CallToolResult{
Content: []json.RawMessage{
[]byte(fmt.Sprintf(`{"type":"text","text":"Unknown tool: %s"}`, input.Request.Name)),
},
}, nil
}
}
```
### Creating a Resource
Example of implementing a resource:
```go
func ListResources(input ListResourcesRequest) (*ListResourcesResult, error) {
return &ListResourcesResult{
Resources: []Resource{
{
URI: "resource://example",
Name: "Example Resource",
Description: ptrString("An example resource"),
MimeType: ptrString("text/plain"),
},
},
}, nil
}
func ReadResource(input ReadResourceRequest) (*ReadResourceResult, error) {
switch input.Request.URI {
case "resource://example":
return &ReadResourceResult{
Contents: []json.RawMessage{
[]byte(`{"uri":"resource://example","mimeType":"text/plain","text":"Resource content here"}`),
},
}, nil
default:
return &ReadResourceResult{
Contents: []json.RawMessage{
[]byte(fmt.Sprintf(`{"type":"text","text":"Unknown resource: %s"}`, input.Request.URI)),
},
}, nil
}
}
```
## Helper Functions
The template includes some useful helper functions for working with pointers:
```go
// Helper to create string pointers
func ptrString(s string) *string {
return &s
}
// Helper to create int64 pointers
func ptrInt64(i int64) *int64 {
return &i
}
// Helper to create float64 pointers
func ptrFloat64(f float64) *float64 {
return &f
}
// Helper to create bool pointers
func ptrBool(b bool) *bool {
return &b
}
```
## Configuration in hyper-mcp
After building and publishing your plugin, configure it in hyper-mcp:
```json
{
"plugins": {
"my_plugin": {
"url": "oci://your-registry/your-plugin-name:latest"
}
}
}
```
For local development/testing:
```json
{
"plugins": {
"my_plugin": {
"url": "file:///path/to/plugin.wasm"
}
}
}
```
## Testing
To test your plugin locally:
1. Build it: `docker build -t my-plugin . && docker run --rm -v $(pwd):/workspace my-plugin cp /plugin.wasm /workspace/`
2. Update hyper-mcp's config to point to `file://` URL
3. Start hyper-mcp with `RUST_LOG=debug`
4. Test through Claude Desktop, Cursor IDE, or another MCP client
## Resources
- [hyper-mcp Documentation](https://github.com/tuananh/hyper-mcp)
- [MCP Protocol Specification](https://spec.modelcontextprotocol.io/)
- [Extism Go PDK](https://github.com/extism/go-pdk)
- [WebAssembly Documentation](https://webassembly.org/)
- [Example Plugins](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins)
## License
Same as hyper-mcp - Apache 2.0
```
--------------------------------------------------------------------------------
/tests/fixtures/unsupported_config.txt:
--------------------------------------------------------------------------------
```
```
--------------------------------------------------------------------------------
/examples/plugins/v1/arxiv/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/context7/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crates-io/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/eval-py/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fetch/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fs/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gitlab/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gomodule/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/maven/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/memory/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/serper/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/sqlite/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/think/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/time/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/templates/plugins/rust/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
```
--------------------------------------------------------------------------------
/src/wasm/mod.rs:
--------------------------------------------------------------------------------
```rust
pub mod http;
pub mod oci;
pub mod s3;
```
--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/src/pdk/mod.rs:
--------------------------------------------------------------------------------
```rust
pub mod exports;
pub mod imports;
pub mod types;
```
--------------------------------------------------------------------------------
/templates/plugins/rust/src/pdk/mod.rs:
--------------------------------------------------------------------------------
```rust
pub mod exports;
pub mod imports;
pub mod types;
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/.cargo/config.toml:
--------------------------------------------------------------------------------
```toml
[build]
target = "wasm32-wasip1"
rustflags = ["--cfg", "tokio_unstable"]
```
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
```toml
[toolchain]
channel = "1.90"
components = ["rustc", "rust-std", "cargo", "clippy", "rustfmt"]
```
--------------------------------------------------------------------------------
/.windsurf/rules/print-ctx-size.md:
--------------------------------------------------------------------------------
```markdown
---
trigger: always_on
description:
globs:
---
# Your rule content
- End every request with "Total context size: ~nk tokens" and list the files you have in view.
```
--------------------------------------------------------------------------------
/tests/fixtures/invalid_plugin_name.yaml:
--------------------------------------------------------------------------------
```yaml
plugins:
plugin@name:
url: "file:///path/to/plugin"
runtime_config:
skip_tools:
- "tool1"
- "tool2"
allowed_hosts:
- "example.com"
valid_plugin:
url: "https://example.com/plugin"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fs/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "fs"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64-serde = "0.7"
base64 = "0.21"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/myip/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "myip"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.21"
base64-serde = "0.8.0"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gomodule/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "gomodule"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64-serde = "0.7"
base64 = "0.21"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/think/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "think"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
base64-serde = "0.8.0"
base64 = "0.22.1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/serper/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "serper"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
base64-serde = "0.8.0"
base64 = "0.22.1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crates-io/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "crates-io"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
base64-serde = "0.8.0"
base64 = "0.22.1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/tool-list-changed/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "tool-list-changed"
version = "0.1.0"
edition = "2021"
[lib]
name = "tool_list_changed"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64-serde = "0.7"
base64 = "0.21"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qr-code/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "mcp-qr-code"
version = "0.1.0"
edition = "2024"
[lib]
name = "qrcode"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64-serde = "0.7"
base64 = "0.21"
qrcode-png = "0.4.1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fetch/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "fetch"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
base64-serde = "0.8.0"
base64 = "0.22.1"
htmd = "0.1.6"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/maven/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "maven"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
quick-xml = "0.31"
extism-pdk = "=1.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
base64-serde = "0.8.0"
base64 = "0.22.1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crypto-price/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM tinygo/tinygo:0.37.0 AS builder
WORKDIR /workspace
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm
FROM scratch
WORKDIR /
COPY --from=builder /workspace/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/github/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM tinygo/tinygo:0.37.0 AS builder
WORKDIR /workspace
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm
FROM scratch
WORKDIR /
COPY --from=builder /workspace/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/templates/plugins/go/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM tinygo/tinygo:0.39.0 AS builder
WORKDIR /workspace
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm
FROM scratch
WORKDIR /
COPY --from=builder /workspace/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/time/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "time"
version = "0.1.0"
edition = "2024"
[lib]
name = "time"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64-serde = "0.7"
base64 = "0.21"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/context7/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "context7"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
base64-serde = "0.8.0"
base64 = "0.22.1"
htmd = "0.1.6"
urlencoding = "2.1.3"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gitlab/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "gitlab"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64-serde = "0.7"
base64 = "0.21"
urlencoding = "2.1"
url = "2.5"
termtree = "0.5.1"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/hash/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "hash"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.21"
base64-serde = "0.8.0"
sha2 = "0.10"
md5 = "0.7"
sha1 = "0.10"
base32 = "0.4"
```
--------------------------------------------------------------------------------
/tests/fixtures/invalid_url.yaml:
--------------------------------------------------------------------------------
```yaml
plugins:
valid_plugin:
url: "file:///path/to/plugin"
runtime_config:
skip_tools:
- "tool1"
- "tool2"
invalid_url_plugin:
url: "not a valid url"
runtime_config:
allowed_hosts:
- "example.com"
another_valid_plugin:
url: "https://example.com/plugin"
```
--------------------------------------------------------------------------------
/templates/plugins/rust/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "plugin"
version = "0.1.0"
edition = "2021"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
anyhow = "1.0"
base64 = "0.22"
base64-serde = "0.8"
chrono = { version = "0.4", features = ["serde"] }
extism-pdk = "1.4.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[workspace]
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "qdrant"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
base64-serde = "0.8.0"
base64 = "0.22.1"
anyhow = "1.0.98"
uuid = { version = "1.16.0", features = ["v4"] }
```
--------------------------------------------------------------------------------
/examples/plugins/v1/arxiv/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "arxiv"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
feed-rs = "1.3"
urlencoding = "2.1"
base64 = "0.21"
base64-serde = "0.8.0"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/sqlite/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "sqlite"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.21"
base64-serde = "0.8.0"
rusqlite = { version = "0.34.0", features = ["bundled"] }
[build-dependencies]
cc = "1.0"
```
--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "rstime"
version = "0.1.0"
edition = "2021"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
anyhow = "1.0"
base64 = "0.21"
base64-serde = "0.7"
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.10"
extism-pdk = "1.4.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[workspace]
```
--------------------------------------------------------------------------------
/examples/plugins/v1/time/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/time.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/arxiv/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/context7/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crates-io/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/eval-py/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fetch/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/fs/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gitlab/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/gomodule/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/hash/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/maven/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/myip/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qr-code/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/qrcode.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/serper/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/think/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.90-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/templates/plugins/rust/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.90-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/memory/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "memory"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.21"
base64-serde = "0.8.0"
uuid = { version = "1.16", features = ["v4", "serde"] }
rusqlite = { version = "0.34.0", features = ["bundled"] }
[build-dependencies]
cc = "1.0"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "meme-generator"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
ab_glyph = "0.2.29"
image = "0.25.6"
imageproc = "0.25.0"
rusttype = "0.9.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.11", features = ["blocking"] }
serde_yaml = "0.9"
extism-pdk = "=1.4.0"
base64-serde = "0.7"
base64 = "0.21"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/tool-list-changed/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-unknown-unknown && \
rustup component add rust-std --target wasm32-unknown-unknown && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-unknown-unknown
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-unknown-unknown/release/tool_list_changed.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/tests/fixtures/invalid_structure.yaml:
--------------------------------------------------------------------------------
```yaml
# This is an invalid structure - missing the 'plugins' top-level map
test_plugin:
url: "file:///path/to/plugin"
runtime_config:
skip_tools:
- "tool1"
- "tool2"
# This puts plugins at the wrong level
configuration:
plugins:
another_plugin:
url: "https://example.com/plugin"
# This has the correct top-level key but incorrect structure
plugins:
- name: minimal_plugin
url: "http://localhost:3000/plugin"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/eval-py/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "eval-py"
version = "0.1.0"
edition = "2024"
[lib]
name = "plugin"
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "=1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rustpython-vm = { version = "0.4.0", default-features = false, features = ["compiler"] }
base64-serde = "0.8.0"
base64 = "0.22.1"
[profile.release]
lto = true
opt-level = 's'
strip = true
[target.wasm32-wasi.dependencies]
getrandom = { version = "0.2", features = ["js"] }
```
--------------------------------------------------------------------------------
/src/logging.rs:
--------------------------------------------------------------------------------
```rust
use once_cell::sync::OnceCell;
use tracing_subscriber::EnvFilter;
static LOGGING: OnceCell<()> = OnceCell::new();
#[ctor::ctor]
fn _install_global_tracing() {
LOGGING.get_or_init(|| {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
)
.with_test_writer()
.with_target(true)
.with_line_number(true)
.with_ansi(false)
.init();
});
}
```
--------------------------------------------------------------------------------
/tests/fixtures/valid_config.yaml:
--------------------------------------------------------------------------------
```yaml
plugins:
test_plugin:
url: "file:///path/to/plugin"
runtime_config:
skip_tools:
- "tool1"
- "tool2"
allowed_hosts:
- "example.com"
- "localhost"
allowed_paths:
- "/tmp"
- "/var/log"
env_vars:
DEBUG: "true"
LOG_LEVEL: "info"
memory_limit: "1GB"
another_plugin:
url: "https://example.com/plugin"
runtime_config:
allowed_hosts:
- "api.example.com"
minimal_plugin:
url: "http://localhost:3000/plugin"
```
--------------------------------------------------------------------------------
/tests/fixtures/invalid_auth_config.yaml:
--------------------------------------------------------------------------------
```yaml
auths:
"https://api.example.com":
type: invalid_type
username: testuser
password: testpass
"https://secure.api.com":
type: basic
# Missing password field
username: testuser
"https://oauth.service.com":
type: token
# Missing token field
"https://malformed.com":
# Missing type field
username: testuser
password: testpass
"https://keyring-incomplete.com":
type: keyring
service: test-service
# Missing user field
plugins:
test_plugin:
url: "file:///path/to/plugin"
```
--------------------------------------------------------------------------------
/tests/fixtures/valid_config.json:
--------------------------------------------------------------------------------
```json
{
"plugins": {
"test_plugin": {
"url": "file:///path/to/plugin",
"runtime_config": {
"skip_tools": ["tool1", "tool2"],
"allowed_hosts": ["example.com", "localhost"],
"allowed_paths": ["/tmp", "/var/log"],
"env_vars": {
"DEBUG": "true",
"LOG_LEVEL": "info"
},
"memory_limit": "1GB"
}
},
"another_plugin": {
"url": "https://example.com/plugin",
"runtime_config": {
"allowed_hosts": ["api.example.com"]
}
},
"minimal_plugin": {
"url": "http://localhost:3000/plugin"
}
}
}
```
--------------------------------------------------------------------------------
/tests/fixtures/config_with_auths.yaml:
--------------------------------------------------------------------------------
```yaml
auths:
"https://api.example.com":
type: basic
username: testuser
password: testpass
"https://secure.api.com":
type: token
token: bearer-token-123
"https://private.registry.io":
type: basic
username: admin
password: secretpass
"https://oauth.service.com":
type: token
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
plugins:
test_plugin:
url: "file:///path/to/plugin"
runtime_config:
allowed_hosts:
- "api.example.com"
- "secure.api.com"
auth_test_plugin:
url: "https://secure.api.com/plugin"
runtime_config:
allowed_hosts:
- "secure.api.com"
```
--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.github.tuananh/hyper-mcp",
"description": "📦️ A fast, secure MCP server that extends its capabilities through WebAssembly plugins",
"status": "active",
"repository": {
"url": "https://github.com/tuananh/hyper-mcp",
"source": "github"
},
"version": "1.0.0",
"packages": [
{
"registry_type": "oci",
"registry_base_url": "https://docker.io",
"identifier": "tuananh/hyper-mcp",
"version": "v0.1.6",
"transport": {
"type": "stdio"
},
"environment_variables": []
}
]
}
```
--------------------------------------------------------------------------------
/tests/fixtures/documentation_example.yaml:
--------------------------------------------------------------------------------
```yaml
auths:
"https://private.registry.io":
type: basic
username: "registry-user"
password: "registry-pass"
"https://api.github.com":
type: token
token: "ghp_1234567890abcdef"
"https://enterprise.api.com":
type: basic
username: "enterprise-user"
password: "enterprise-pass"
plugins:
time:
url: oci://ghcr.io/tuananh/time-plugin:latest
myip:
url: oci://ghcr.io/tuananh/myip-plugin:latest
runtime_config:
allowed_hosts:
- "1.1.1.1"
skip_tools:
- "debug"
env_vars:
FOO: "bar"
memory_limit: "512Mi"
private_plugin:
url: "https://private.registry.io/my_plugin"
runtime_config:
allowed_hosts:
- "private.registry.io"
```
--------------------------------------------------------------------------------
/tests/fixtures/keyring_auth_config.yaml:
--------------------------------------------------------------------------------
```yaml
auths:
"https://private.registry.io":
type: keyring
service: "private-registry"
user: "registry-user"
"https://internal.api.com":
type: keyring
service: "internal-api"
user: "api-user"
"https://secure.vault.com":
type: keyring
service: "vault-service"
user: "vault-admin"
"https://enterprise.github.com":
type: keyring
service: "github-enterprise"
user: "enterprise-user"
plugins:
secure-plugin:
url: "https://private.registry.io/plugin"
runtime_config:
allowed_hosts:
- "private.registry.io"
internal-plugin:
url: "https://internal.api.com/plugin"
runtime_config:
allowed_hosts:
- "internal.api.com"
- "secure.vault.com"
env_vars:
KEYRING_SERVICE: "internal-api"
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM --platform=$BUILDPLATFORM rust:1.90 AS builder
WORKDIR /app
RUN cargo install cargo-auditable
COPY Cargo.toml Cargo.lock ./
RUN cargo fetch
COPY src ./src
RUN cargo auditable build --release --locked
FROM debian:13-slim
LABEL org.opencontainers.image.authors="[email protected]" \
org.opencontainers.image.url="https://github.com/tuananh/hyper-mcp" \
org.opencontainers.image.source="https://github.com/tuananh/hyper-mcp" \
org.opencontainers.image.vendor="github.com/tuananh/hyper-mcp" \
io.modelcontextprotocol.server.name="io.github.tuananh/hyper-mcp"
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/target/release/hyper-mcp /usr/local/bin/hyper-mcp
ENTRYPOINT ["/usr/local/bin/hyper-mcp"]
```
--------------------------------------------------------------------------------
/tests/fixtures/config_with_auths.json:
--------------------------------------------------------------------------------
```json
{
"auths": {
"https://api.example.com": {
"type": "basic",
"username": "testuser",
"password": "testpass"
},
"https://secure.api.com": {
"type": "token",
"token": "bearer-token-123"
},
"https://private.registry.io": {
"type": "basic",
"username": "admin",
"password": "secretpass"
},
"https://oauth.service.com": {
"type": "token",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
}
},
"plugins": {
"test_plugin": {
"url": "file:///path/to/plugin",
"runtime_config": {
"allowed_hosts": ["api.example.com", "secure.api.com"]
}
},
"auth_test_plugin": {
"url": "https://secure.api.com/plugin",
"runtime_config": {
"allowed_hosts": ["secure.api.com"]
}
}
}
}
```
--------------------------------------------------------------------------------
/tests/fixtures/documentation_example.json:
--------------------------------------------------------------------------------
```json
{
"auths": {
"https://private.registry.io": {
"type": "basic",
"username": "registry-user",
"password": "registry-pass"
},
"https://api.github.com": {
"type": "token",
"token": "ghp_1234567890abcdef"
},
"https://enterprise.api.com": {
"type": "basic",
"username": "enterprise-user",
"password": "enterprise-pass"
}
},
"plugins": {
"time": {
"url": "oci://ghcr.io/tuananh/time-plugin:latest"
},
"myip": {
"url": "oci://ghcr.io/tuananh/myip-plugin:latest",
"runtime_config": {
"allowed_hosts": ["1.1.1.1"],
"skip_tools": ["debug"],
"env_vars": { "FOO": "bar" },
"memory_limit": "512Mi"
}
},
"private_plugin": {
"url": "https://private.registry.io/my_plugin",
"runtime_config": {
"allowed_hosts": ["private.registry.io"]
}
}
}
}
```
--------------------------------------------------------------------------------
/src/wasm/http.rs:
--------------------------------------------------------------------------------
```rust
use crate::{config::AuthConfig, https_auth::Authenticator};
use anyhow::{Result, anyhow};
use reqwest::Client;
use std::collections::HashMap;
use tokio::sync::OnceCell;
use url::Url;
static REQWEST_CLIENT: OnceCell<Client> = OnceCell::const_new();
pub async fn load_wasm(url: &Url, auths: &Option<HashMap<Url, AuthConfig>>) -> Result<Vec<u8>> {
match url.scheme() {
"http" => Ok(REQWEST_CLIENT
.get_or_init(|| async { reqwest::Client::new() })
.await
.get(url.as_str())
.send()
.await?
.bytes()
.await?
.to_vec()),
"https" => Ok(REQWEST_CLIENT
.get_or_init(|| async { reqwest::Client::new() })
.await
.get(url.as_str())
.add_auth(auths, url)
.send()
.await?
.bytes()
.await?
.to_vec()),
_ => Err(anyhow!("Unsupported URL scheme: {}", url.scheme())),
}
}
```
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
```
{
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: [
'config:recommended',
],
schedule: [
'on monday',
],
packageRules: [
{
matchDepTypes: [
'action',
],
pinDigests: true,
},
{
extends: [
'helpers:pinGitHubActionDigests',
],
extractVersion: '^(?<version>v?\\d+\\.\\d+\\.\\d+)$',
versioning: 'regex:^v?(?<major>\\d+)(\\.(?<minor>\\d+)\\.(?<patch>\\d+))?$',
},
{
matchManagers: [
'github-actions',
],
groupName: 'GitHub Actions',
labels: [
'dependencies',
'github-actions',
],
commitMessagePrefix: 'github-actions',
"rangeStrategy": "pin",
},
{
matchManagers: [
'cargo',
],
groupName: 'Rust dependencies',
labels: [
'dependencies',
'rust',
],
commitMessagePrefix: 'rust',
},
],
prHourlyLimit: 4,
prConcurrentLimit: 16,
dependencyDashboard: true,
}
```
--------------------------------------------------------------------------------
/config.example.json:
--------------------------------------------------------------------------------
```json
{
"plugins": {
"time": {
"url": "oci://ghcr.io/tuananh/time-plugin:latest"
},
"qr-code": {
"url": "oci://ghcr.io/tuananh/qrcode-plugin:latest"
},
"hash": {
"url": "oci://ghcr.io/tuananh/hash-plugin:latest"
},
"myip": {
"url": "oci://ghcr.io/tuananh/myip-plugin:latest",
"runtime_config": {
"allowed_hosts": ["1.1.1.1"],
"skip_tools": ["debug_.*", ".*_test"]
}
},
"fetch": {
"url": "oci://ghcr.io/tuananh/fetch-plugin:latest",
"runtime_config": {
"allowed_hosts": ["*"],
"skip_tools": ["temp_.*", "mock_.*", "tool_[0-9]+"]
}
},
"example_plugin": {
"url": "oci://ghcr.io/example/demo-plugin:latest",
"runtime_config": {
"skip_tools": [
"admin_tool",
"dev_.*",
".*_deprecated",
"test_(unit|integration)",
"[a-z]+_helper"
],
"allowed_hosts": ["example.com"],
"memory_limit": "256Mi"
}
}
}
}
```
--------------------------------------------------------------------------------
/.windsurf/rules/think.md:
--------------------------------------------------------------------------------
```markdown
---
trigger: model_decision
---
After any context change (viewing new files, running commands, or receiving tool outputs), use the "think" tool to organize your reasoning before responding.
Specifically, always use the think tool when:
- After examining file contents or project structure
- After running terminal commands or analyzing their outputs
- After receiving search results or API responses
- Before making code suggestions or explaining complex concepts
- When transitioning between different parts of a task
When using the think tool:
- List the specific rules or constraints that apply to the current task
- Check if all required information is collected
- Verify that your planned approach is correct
- Break down complex problems into clearly defined steps
- Analyze outputs from other tools thoroughly
- Plan multi-step approaches before executing them
The think tool has been proven to improve performance by up to 54% on complex tasks, especially when working with multiple tools or following detailed policies.
```
--------------------------------------------------------------------------------
/src/wasm/s3.rs:
--------------------------------------------------------------------------------
```rust
use anyhow::Result;
use aws_sdk_s3::Client;
use tokio::sync::OnceCell;
use url::Url;
static S3_CLIENT: OnceCell<Client> = OnceCell::const_new();
pub async fn load_wasm(url: &Url) -> Result<Vec<u8>> {
let bucket = url
.host_str()
.ok_or_else(|| anyhow::anyhow!("S3 URL must have a valid bucket name in the host"))?;
let key = url.path().trim_start_matches('/');
match S3_CLIENT
.get_or_init(|| async { Client::new(&aws_config::load_from_env().await) })
.await
.get_object()
.bucket(bucket)
.key(key)
.send()
.await
{
Ok(response) => match response.body.collect().await {
Ok(body) => Ok(body.to_vec()),
Err(e) => {
tracing::error!("Failed to collect S3 object body: {e}");
Err(anyhow::anyhow!("Failed to collect S3 object body: {e}"))
}
},
Err(e) => {
tracing::error!("Failed to get object from S3: {e}");
Err(anyhow::anyhow!("Failed to get object from S3: {e}"))
}
}
}
```
--------------------------------------------------------------------------------
/config.example.yaml:
--------------------------------------------------------------------------------
```yaml
---
plugins:
time:
url: oci://ghcr.io/tuananh/time-plugin:latest
qr-code:
url: oci://ghcr.io/tuananh/qrcode-plugin:latest
hash:
url: oci://ghcr.io/tuananh/hash-plugin:latest
myip:
url: oci://ghcr.io/tuananh/myip-plugin:latest
runtime_config:
allowed_hosts:
- "1.1.1.1"
skip_tools:
- "debug_.*" # Skip all debug tools
- ".*_test" # Skip all test tools
fetch:
url: oci://ghcr.io/tuananh/fetch-plugin:latest
runtime_config:
allowed_hosts:
- "*"
skip_tools:
- "temp_.*" # Skip temporary tools
- "mock_.*" # Skip mock tools
- "tool_[0-9]+" # Skip numbered tools like "tool_1", "tool_42"
# Example plugin with comprehensive skip_tools patterns
example_plugin:
url: oci://ghcr.io/example/demo-plugin:latest
runtime_config:
skip_tools:
- "admin_tool" # Skip exact tool name
- "dev_.*" # Skip development tools
- ".*_deprecated" # Skip deprecated tools
- "test_(unit|integration)" # Skip specific test types
- "[a-z]+_helper" # Skip lowercase helpers
allowed_hosts:
- "example.com"
memory_limit: "256Mi"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/memory/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
# Install wasi-sdk
ENV WASI_OS=linux \
WASI_VERSION=25 \
WASI_VERSION_FULL=25.0
# Detect architecture and set WASI_ARCH accordingly
RUN apt-get update && apt-get install -y wget && \
ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
export WASI_ARCH=x86_64; \
elif [ "$ARCH" = "aarch64" ]; then \
export WASI_ARCH=arm64; \
else \
echo "Unsupported architecture: $ARCH" && exit 1; \
fi && \
cd /opt && \
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz && \
tar xvf wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz && \
rm wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz && \
mv wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS} wasi-sdk
WORKDIR /workspace
COPY . .
RUN cargo fetch
ENV WASI_SDK_PATH=/opt/wasi-sdk
ENV CC_wasm32_wasip1="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot"
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/sqlite/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
# Install wasi-sdk
ENV WASI_OS=linux \
WASI_VERSION=25 \
WASI_VERSION_FULL=25.0
# Detect architecture and set WASI_ARCH accordingly
RUN apt-get update && apt-get install -y wget && \
ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
export WASI_ARCH=x86_64; \
elif [ "$ARCH" = "aarch64" ]; then \
export WASI_ARCH=arm64; \
else \
echo "Unsupported architecture: $ARCH" && exit 1; \
fi && \
cd /opt && \
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz && \
tar xvf wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz && \
rm wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS}.tar.gz && \
mv wasi-sdk-${WASI_VERSION_FULL}-${WASI_ARCH}-${WASI_OS} wasi-sdk
WORKDIR /workspace
COPY . .
RUN cargo fetch
ENV WASI_SDK_PATH=/opt/wasi-sdk
ENV CC_wasm32_wasip1="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot"
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
--------------------------------------------------------------------------------
/examples/plugins/v1/myip/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
use extism_pdk::*;
use pdk::types::*;
use serde_json::json;
pub(crate) fn call(_input: CallToolRequest) -> Result<CallToolResult, Error> {
let request = HttpRequest::new("https://1.1.1.1/cdn-cgi/trace");
let response = http::request::<Vec<u8>>(&request, None)
.map_err(|e| Error::msg(format!("Failed to make HTTP request: {}", e)))?;
let text = String::from_utf8(response.body().to_vec())
.map_err(|e| Error::msg(format!("Failed to parse response as UTF-8: {}", e)))?;
// Parse the response to extract IP address
let ip = text
.lines()
.find(|line| line.starts_with("ip="))
.map(|line| line.trim_start_matches("ip="))
.ok_or_else(|| Error::msg("Could not find IP address in response"))?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(ip.to_string()),
mime_type: Some("text/plain".into()),
r#type: ContentType::Text,
data: None,
}],
})
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult {
tools: vec![ToolDescription {
name: "myip".into(),
description: "Get the current IP address using Cloudflare's service".into(),
input_schema: json!({
"type": "object",
"properties": {},
"required": [],
})
.as_object()
.unwrap()
.clone(),
}],
})
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/crypto-price/main.go:
--------------------------------------------------------------------------------
```go
package main
import (
"encoding/json"
"errors"
"fmt"
"strings"
pdk "github.com/extism/go-pdk"
)
func Call(input CallToolRequest) (CallToolResult, error) {
args := input.Params.Arguments
if args == nil {
return CallToolResult{}, errors.New("Arguments must be provided")
}
argsMap := args.(map[string]interface{})
fmt.Println("argsMap", argsMap)
return getCryptoPrice(argsMap)
}
func getCryptoPrice(args map[string]interface{}) (CallToolResult, error) {
symbol, ok := args["symbol"].(string)
if !ok {
return CallToolResult{}, errors.New("symbol must be provided")
}
// Convert symbol to uppercase
symbol = strings.ToUpper(symbol)
// Use CoinGecko API to get the price
url := fmt.Sprintf("https://api.coingecko.com/api/v3/simple/price?ids=%s&vs_currencies=usd", strings.ToLower(symbol))
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
resp := req.Send()
var result map[string]map[string]float64
if err := json.Unmarshal(resp.Body(), &result); err != nil {
return CallToolResult{}, fmt.Errorf("failed to parse response: %v", err)
}
if price, ok := result[strings.ToLower(symbol)]["usd"]; ok {
priceStr := fmt.Sprintf("%.2f USD", price)
return CallToolResult{
Content: []Content{
{
Type: ContentTypeText,
Text: &priceStr,
},
},
}, nil
}
return CallToolResult{}, fmt.Errorf("price not found for %s", symbol)
}
func Describe() (ListToolsResult, error) {
return ListToolsResult{
Tools: []ToolDescription{
{
Name: "crypto-price",
Description: "Get the current price of a cryptocurrency in USD",
InputSchema: map[string]interface{}{
"type": "object",
"required": []string{"symbol"},
"properties": map[string]interface{}{
"symbol": map[string]interface{}{
"type": "string",
"description": "the cryptocurrency symbol/id (e.g., bitcoin, ethereum)",
},
},
},
},
},
}, nil
}
```
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
```toml
[package]
name = "hyper-mcp"
version = "0.1.8"
edition = "2024"
authors = ["Tuan Anh Tran <[email protected]>"]
description = " A fast, secure MCP server that extends its capabilities through WebAssembly plugins"
keywords = ["rust", "ai", "mcp", "cli"]
categories = ["command-line-utilities"]
readme = "README.md"
license = "Apache-2.0"
repository = "https://github.com/tuananh/hyper-mcp"
homepage = "https://github.com/tuananh/hyper-mcp"
documentation = "https://github.com/tuananh/hyper-mcp"
[dependencies]
anyhow = "1.0.98"
async-trait = "0.1"
aws-config = { version = "1.8.2", features = ["behavior-version-latest"] }
aws-sdk-s3 = "1.98.0"
axum = "0.8.4"
bytesize = "2.0.1"
clap = { version = "4.5.40", features = ["derive", "env"] }
ctor = "0.6"
dashmap = "6.1.0"
dirs = "6.0.0"
docker_credential = "1.3.2"
extism = "1.12.0"
extism-convert = "1.12.0"
flate2 = "1.1.2"
hex = "0.4.3"
keyring = { version = "3.6.3", features = [
"apple-native",
"linux-native",
"windows-native",
] }
oci-client = "0.15.0"
once_cell = "1.21.3"
rmcp = { version = "0.9.0", features = [
"elicitation",
"server",
"transport-io",
"transport-sse-server",
"transport-streamable-http-server",
] }
regex = { version = "1.11.3", features = ["unicode", "perf"] }
reqwest = { version = "0.12.21", features = ["json"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
serde_yaml = "0.9.34"
serde_with = "3.15"
sha2 = "0.10.9"
sigstore = { version = "0.13.0", features = ["cosign", "verify", "bundle"] }
tar = "0.4.44"
tokio = { version = "1.45.1", features = ["full"] }
tokio-util = "0.7"
toml = "0.9.0"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
url = { version = "2", features = ["serde"] }
uuid = { version = "1.18", features = ["serde"] }
[dev-dependencies]
futures = "0.3.31"
rmcp = { version = "0.9.0", features = [
"client",
"transport-async-rw",
] }
tempfile = "3.12.0"
tokio-test = "0.4.4"
tokio-util = "0.7.16"
[[bin]]
name = "hyper-mcp"
path = "src/main.rs"
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/generate_embedded.py:
--------------------------------------------------------------------------------
```python
import os
import json
def main():
# Read templates.json to get list of template IDs
with open('templates.json', 'r') as f:
templates = json.load(f)
template_ids = [t['id'] for t in templates]
# Start generating embedded.rs
output = []
output.append('// Embed templates.json')
output.append('pub const TEMPLATES_JSON: &str = include_str!("../templates.json");')
output.append('')
output.append('// Embed font data')
output.append('pub const FONT_DATA: &[u8] = include_bytes!("../assets/fonts/TitilliumWeb-Black.ttf");')
output.append('')
output.append('// Function to get template config')
output.append('pub fn get_template_config(template_id: &str) -> Option<&\'static str> {')
output.append(' match template_id {')
# Add template configs
for template_id in template_ids:
config_path = f'assets/templates/{template_id}/config.yml'
if os.path.exists(config_path):
output.append(f' "{template_id}" => Some(include_str!("../assets/templates/{template_id}/config.yml")),')
output.append(' _ => None')
output.append(' }')
output.append('}')
output.append('')
# Add template images
output.append('// Function to get template image')
output.append('pub fn get_template_image(template_id: &str, image_name: &str) -> Option<&\'static [u8]> {')
output.append(' match (template_id, image_name) {')
for template_id in template_ids:
template_dir = f'assets/templates/{template_id}'
if os.path.exists(template_dir):
for file in os.listdir(template_dir):
if file.endswith(('.jpg', '.png', '.gif')):
output.append(f' ("{template_id}", "{file}") => Some(include_bytes!("../assets/templates/{template_id}/{file}")),')
output.append(' _ => None')
output.append(' }')
output.append('}')
# Write output
with open('src/embedded.rs', 'w') as f:
f.write('\n'.join(output))
if __name__ == '__main__':
main()
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
submodules: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Install WASM target
run: rustup target add wasm32-wasip1
- name: Cache dependencies
uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "., examples/plugins/v1/*"
- name: Run clippy
run: cargo clippy -- -D warnings
- name: Check formatting
run: cargo fmt -- --check
- name: Run tests
run: cargo test --workspace --all-features
- name: Build hyper-mcp
run: cargo build
- name: Set up TinyGo
uses: acifani/setup-tinygo@db56321a62b9a67922bb9ac8f9d085e218807bb3 # v2.0.1
with:
tinygo-version: "0.37.0"
- name: Build example plugins
run: |
for plugin in examples/plugins/v{1,2}/*/; do
plugin_name=$(basename $plugin)
echo "Building plugin: $plugin"
current_dir="$PWD"
cd $plugin
case "$plugin_name" in
"crypto-price"|"github")
# --- Go-based plugins ---
GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm
;;
"arxiv"|"context7"|"crates-io"|"crypto-price"|"eval-py"|"fetch"|"fs"|"gitlab"|"gomodule"|"hash"|"maven"|"meme-generator"|"myip"|"qdrant"|"qr-code"|"rstime"|"serper"|"think"|"time"|"tool-list-changed")
# --- Rust-based plugins ---
cargo build --release --target wasm32-wasip1
;;
*)
echo "Skipping: $plugin_name"
esac
cd $current_dir
done
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qr-code/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
use base64::Engine;
use extism_pdk::*;
use pdk::types::*;
use qrcode_png::{Color, QrCode, QrCodeEcc};
use serde_json::{Map, Value, json};
pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
extism_pdk::log!(
LogLevel::Info,
"called with args: {:?}",
input.params.arguments
);
let args = input.params.arguments.unwrap_or_default();
let ecc = to_ecc(
args.get("ecc")
.cloned()
.unwrap_or_else(|| json!(4))
.as_number()
.unwrap()
.is_u64() as u8,
);
let data = match args.get("data") {
Some(v) => v.as_str().unwrap(),
None => return Err(Error::msg("`data` must be available")),
};
let mut code = QrCode::new(data, ecc)?;
code.margin(10);
code.zoom(10);
let b = code.generate(Color::Grayscale(0, 255))?;
let data = base64::engine::general_purpose::STANDARD.encode(b);
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: None,
mime_type: Some("image/png".into()),
r#type: ContentType::Image,
data: Some(data),
}],
})
}
fn to_ecc(num: u8) -> QrCodeEcc {
if num < 4 {
return unsafe { std::mem::transmute::<u8, QrCodeEcc>(num) };
}
QrCodeEcc::High
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult {
tools: vec![ToolDescription {
name: "qr-code".into(),
description: "Convert data like a message or URL to a QR code (resulting in a PNG file)".into(),
input_schema: json!({
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "data to convert to a QR code PNG"
},
"ecc": {
"type": "number",
"description": "Error correction level (range from 1 [low] to 4 [high], default to 4 unless user specifies)"
}
},
"required": ["data"]
}).as_object().unwrap().clone(),
}],
})
}
```
--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/src/pdk/exports.rs:
--------------------------------------------------------------------------------
```rust
use extism_pdk::{Error, Json, Memory, extism::error_set, input, output};
pub(crate) fn return_error(e: Error) -> i32 {
let err = format!("{:?}", e);
let mem = Memory::from_bytes(&err).unwrap();
unsafe {
error_set(mem.offset());
}
-1
}
macro_rules! try_input_json {
() => {{
let x = input();
match x {
Ok(Json(x)) => x,
Err(e) => return return_error(e),
}
}};
}
#[no_mangle]
pub extern "C" fn call_tool() -> i32 {
let ret = crate::call_tool(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn complete() -> i32 {
let ret = crate::complete(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn get_prompt() -> i32 {
let ret = crate::get_prompt(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_prompts() -> i32 {
let ret = crate::list_prompts(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_resource_templates() -> i32 {
let ret = crate::list_resource_templates(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_resources() -> i32 {
let ret = crate::list_resources(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_tools() -> i32 {
let ret = crate::list_tools(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn on_roots_list_changed() -> i32 {
let ret = crate::on_roots_list_changed(try_input_json!()).and_then(output);
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn read_resource() -> i32 {
let ret = crate::read_resource(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
```
--------------------------------------------------------------------------------
/templates/plugins/rust/src/pdk/exports.rs:
--------------------------------------------------------------------------------
```rust
use extism_pdk::{Error, Json, Memory, extism::error_set, input, output};
pub(crate) fn return_error(e: Error) -> i32 {
let err = format!("{:?}", e);
let mem = Memory::from_bytes(&err).unwrap();
unsafe {
error_set(mem.offset());
}
-1
}
macro_rules! try_input_json {
() => {{
let x = input();
match x {
Ok(Json(x)) => x,
Err(e) => return return_error(e),
}
}};
}
#[no_mangle]
pub extern "C" fn call_tool() -> i32 {
let ret = crate::call_tool(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn complete() -> i32 {
let ret = crate::complete(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn get_prompt() -> i32 {
let ret = crate::get_prompt(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_prompts() -> i32 {
let ret = crate::list_prompts(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_resource_templates() -> i32 {
let ret = crate::list_resource_templates(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_resources() -> i32 {
let ret = crate::list_resources(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn list_tools() -> i32 {
let ret = crate::list_tools(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn on_roots_list_changed() -> i32 {
let ret = crate::on_roots_list_changed(try_input_json!()).and_then(output);
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
#[no_mangle]
pub extern "C" fn read_resource() -> i32 {
let ret = crate::read_resource(try_input_json!()).and_then(|x| output(Json(x)));
match ret {
Ok(()) => 0,
Err(e) => return_error(e),
}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/think/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
use extism_pdk::*;
use json::Value;
use pdk::types::{
CallToolRequest, CallToolResult, Content, ContentType, ListToolsResult, ToolDescription,
};
use serde_json::json;
pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
match input.params.name.as_str() {
"think" => think(input),
_ => Ok(CallToolResult {
is_error: Some(true),
content: vec![Content {
annotations: None,
text: Some(format!("Unknown tool: {}", input.params.name)),
mime_type: None,
r#type: ContentType::Text,
data: None,
}],
}),
}
}
fn think(input: CallToolRequest) -> Result<CallToolResult, Error> {
let args = input.params.arguments.unwrap_or_default();
if let Some(Value::String(thought)) = args.get("thought") {
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(thought.clone()),
mime_type: Some("text/plain".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
} else {
Ok(CallToolResult {
is_error: Some(true),
content: vec![Content {
annotations: None,
text: Some("Please provide a 'thought' string.".into()),
mime_type: None,
r#type: ContentType::Text,
data: None,
}],
})
}
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult{
tools: vec![
ToolDescription {
name: "think".into(),
description: "Use the tool to think about something. It will not obtain new information or change the database, but just append the thought to the log. Use it when complex reasoning or some cache memory is needed.".into(),
input_schema: json!({
"type": "object",
"properties": {
"thought": {
"type": "string",
"description": "A thought to think about.",
},
},
"required": ["thought"],
})
.as_object()
.unwrap()
.clone(),
},
],
})
}
```
--------------------------------------------------------------------------------
/src/cli.rs:
--------------------------------------------------------------------------------
```rust
use clap::Parser;
use std::path::PathBuf;
pub const DEFAULT_BIND_ADDRESS: &str = "127.0.0.1:3001";
#[derive(Parser, Clone)]
#[command(author = "Tuan Anh Tran <[email protected]>", version = env!("CARGO_PKG_VERSION"), about, long_about = None)]
pub struct Cli {
#[arg(short, long, value_name = "FILE")]
pub config_file: Option<PathBuf>,
#[arg(
long = "transport",
value_name = "TRANSPORT",
env = "HYPER_MCP_TRANSPORT",
default_value = "stdio",
value_parser = ["stdio", "sse", "streamable-http"]
)]
pub transport: String,
#[arg(
long = "bind-address",
value_name = "ADDRESS",
env = "HYPER_MCP_BIND_ADDRESS",
default_value = DEFAULT_BIND_ADDRESS
)]
pub bind_address: String,
#[arg(
long = "insecure-skip-signature",
help = "Skip OCI image signature verification. Will override the value in your config file if set.",
env = "HYPER_MCP_INSECURE_SKIP_SIGNATURE"
)]
pub insecure_skip_signature: Option<bool>,
#[arg(
long = "use-sigstore-tuf-data",
help = "Use Sigstore TUF data for OCI verification. Will override the value in your config file if set.",
env = "HYPER_MCP_USE_SIGSTORE_TUF_DATA"
)]
pub use_sigstore_tuf_data: Option<bool>,
#[arg(
long = "rekor-pub-keys",
help = "Path to Rekor public keys for OCI verification. Will override the value in your config file if set.",
env = "HYPER_MCP_REKOR_PUB_KEYS"
)]
pub rekor_pub_keys: Option<PathBuf>,
#[arg(
long = "fulcio-certs",
help = "Path to Fulcio certificates for OCI verification. Will override the value in your config file if set.",
env = "HYPER_MCP_FULCIO_CERTS"
)]
pub fulcio_certs: Option<PathBuf>,
#[arg(
long = "cert-issuer",
help = "Certificate issuer to verify OCI against. Will override the value in your config file if set.",
env = "HYPER_MCP_CERT_ISSUER"
)]
pub cert_issuer: Option<String>,
#[arg(
long = "cert-email",
help = "Certificate email to verify OCI against. Will override the value in your config file if set.",
env = "HYPER_MCP_CERT_EMAIL"
)]
pub cert_email: Option<String>,
#[arg(
long = "cert-url",
help = "Certificate URL to verify OCI against. Will override the value in your config file if set.",
env = "HYPER_MCP_CERT_URL"
)]
pub cert_url: Option<String>,
}
impl Default for Cli {
fn default() -> Self {
Self {
config_file: None,
transport: "stdio".to_string(),
bind_address: DEFAULT_BIND_ADDRESS.to_string(),
insecure_skip_signature: None,
use_sigstore_tuf_data: None,
rekor_pub_keys: None,
fulcio_certs: None,
cert_issuer: None,
cert_email: None,
cert_url: None,
}
}
}
```
--------------------------------------------------------------------------------
/templates/plugins/rust/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
use anyhow::{Result, anyhow};
use pdk::types::*;
pub(crate) fn call_tool(_input: CallToolRequest) -> Result<CallToolResult> {
Err(anyhow!("call_tool not implemented"))
}
// Provide completion suggestions for a partially-typed input.
//
// This function is called when the user requests autocompletion. The plugin should analyze the partial input and return matching completion suggestions based on the reference (prompt or resource) and argument context.
pub(crate) fn complete(_input: CompleteRequest) -> Result<CompleteResult> {
Ok(CompleteResult::default())
}
// Retrieve a specific prompt by name.
//
// This function is called when the user requests a specific prompt. The plugin should return the prompt details including messages and optional description.
pub(crate) fn get_prompt(_input: GetPromptRequest) -> Result<GetPromptResult> {
Err(anyhow!("get_prompt not implemented"))
}
// List all available prompts.
//
// This function should return a list of prompts that the plugin provides. Each prompt should include its name and a brief description of what it does. Supports pagination via cursor.
pub(crate) fn list_prompts(_input: ListPromptsRequest) -> Result<ListPromptsResult> {
Ok(ListPromptsResult::default())
}
// List all available resource templates.
//
// This function should return a list of resource templates that the plugin provides. Templates are URI patterns that can match multiple resources. Supports pagination via cursor.
pub(crate) fn list_resource_templates(
_input: ListResourceTemplatesRequest,
) -> Result<ListResourceTemplatesResult> {
Ok(ListResourceTemplatesResult::default())
}
// List all available resources.
//
// This function should return a list of resources that the plugin provides. Resources are URI-based references to files, data, or services. Supports pagination via cursor.
pub(crate) fn list_resources(_input: ListResourcesRequest) -> Result<ListResourcesResult> {
Ok(ListResourcesResult::default())
}
// List all available tools.
//
// This function should return a list of all tools that the plugin provides. Each tool should include its name, description, and input schema. Supports pagination via cursor.
pub(crate) fn list_tools(_input: ListToolsRequest) -> Result<ListToolsResult> {
Ok(ListToolsResult::default())
}
// Notification that the list of roots has changed.
//
// This is an optional notification handler. If implemented, the plugin will be notified whenever the roots list changes on the client side. This allows plugins to react to changes in the file system roots or other root resources.
pub(crate) fn on_roots_list_changed(_input: PluginNotificationContext) -> Result<()> {
Ok(())
}
// Read the contents of a resource by its URI.
//
// This function is called when the user wants to read the contents of a specific resource. The plugin should retrieve and return the resource data with appropriate MIME type information.
pub(crate) fn read_resource(_input: ReadResourceRequest) -> Result<ReadResourceResult> {
Err(anyhow!("read_resource not implemented"))
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/hash/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
use base64::Engine;
use extism_pdk::*;
use pdk::types::*;
use serde_json::json;
use sha1::Sha1;
use sha2::{Digest, Sha224, Sha256, Sha384, Sha512};
// Called when the tool is invoked.
pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
extism_pdk::log!(
LogLevel::Info,
"called with args: {:?}",
input.params.arguments
);
let args = input.params.arguments.unwrap_or_default();
let data = match args.get("data") {
Some(v) => v.as_str().unwrap(),
None => return Err(Error::msg("`data` is required")),
};
let algorithm = match args.get("algorithm") {
Some(v) => v.as_str().unwrap(),
None => return Err(Error::msg("`algorithm` is required")),
};
let result = match algorithm {
"sha256" => {
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
format!("{:x}", hasher.finalize())
}
"sha512" => {
let mut hasher = Sha512::new();
hasher.update(data.as_bytes());
format!("{:x}", hasher.finalize())
}
"sha384" => {
let mut hasher = Sha384::new();
hasher.update(data.as_bytes());
format!("{:x}", hasher.finalize())
}
"sha224" => {
let mut hasher = Sha224::new();
hasher.update(data.as_bytes());
format!("{:x}", hasher.finalize())
}
"sha1" => {
let mut hasher = Sha1::new();
hasher.update(data.as_bytes());
format!("{:x}", hasher.finalize())
}
"md5" => {
format!("{:x}", md5::compute(data))
}
"base32" => base32::encode(base32::Alphabet::RFC4648 { padding: true }, data.as_bytes()),
"base64" | _ => base64::engine::general_purpose::STANDARD.encode(data),
};
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(result),
mime_type: Some("text/plain".into()),
r#type: ContentType::Text,
data: None,
}],
})
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult {
tools: vec![ToolDescription {
name: "hash".into(),
description: "Hash data using various algorithms: sha256, sha512, sha384, sha224, sha1, md5, base32, base64".into(),
input_schema: json!({
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "data to convert to hash or encoded format"
},
"algorithm": {
"type": "string",
"description": "algorithm to use for hashing or encoding",
"enum": ["sha256", "sha512", "sha384", "sha224", "sha1", "md5", "base32", "base64"]
}
},
"required": ["data", "algorithm"]
}).as_object().unwrap().clone(),
}],
})
}
```
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
```rust
mod cli;
mod config;
mod https_auth;
mod logging;
mod naming;
mod plugin;
mod service;
mod wasm;
use anyhow::Result;
use clap::Parser;
use rmcp::transport::sse_server::SseServer;
use rmcp::transport::streamable_http_server::{
StreamableHttpService, session::local::LocalSessionManager,
};
use rmcp::{ServiceExt, transport::stdio};
use tokio::{runtime::Handle, task::block_in_place};
#[tokio::main]
async fn main() -> Result<()> {
let cli = cli::Cli::parse();
let config = config::load_config(&cli).await?;
tracing::info!("Starting hyper-mcp server");
match cli.transport.as_str() {
"stdio" => {
tracing::info!("Starting hyper-mcp with stdio transport");
let service = service::PluginService::new(&config)
.await?
.serve(stdio())
.await
.inspect_err(|e| {
tracing::error!("Serving error: {:?}", e);
})?;
service.waiting().await?;
}
"sse" => {
tracing::info!(
"Starting hyper-mcp with SSE transport at {}",
cli.bind_address
);
let ct = SseServer::serve(cli.bind_address.parse()?)
.await?
.with_service({
move || {
block_in_place(|| {
Handle::current()
.block_on(async { service::PluginService::new(&config).await })
})
.expect("Failed to create plugin service")
}
});
tokio::signal::ctrl_c().await?;
ct.cancel();
}
"streamable-http" => {
let bind_address = cli.bind_address.clone();
tracing::info!(
"Starting hyper-mcp with streamable-http transport at {}/mcp",
bind_address
);
let service = StreamableHttpService::new(
{
move || {
block_in_place(|| {
Handle::current()
.block_on(async { service::PluginService::new(&config).await })
})
.map_err(std::io::Error::other)
}
},
LocalSessionManager::default().into(),
Default::default(),
);
let router = axum::Router::new().nest_service("/mcp", service);
let listener = tokio::net::TcpListener::bind(bind_address.clone()).await?;
let _ = axum::serve(listener, router)
.with_graceful_shutdown(async {
tokio::signal::ctrl_c().await.unwrap();
tracing::info!("Received Ctrl+C, shutting down hyper-mcp server...");
// Give the log a moment to flush
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
std::process::exit(0);
})
.await;
}
_ => unreachable!(),
}
Ok(())
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/serper/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
use std::collections::BTreeMap;
use extism_pdk::*;
use json::Value;
use pdk::types::{
CallToolRequest, CallToolResult, Content, ContentType, ListToolsResult, ToolDescription,
};
use serde_json::json;
pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
match input.params.name.as_str() {
"serper_web_search" => serper_web_search(input),
_ => Ok(CallToolResult {
is_error: Some(true),
content: vec![Content {
annotations: None,
text: Some(format!("Unknown tool: {}", input.params.name)),
mime_type: None,
r#type: ContentType::Text,
data: None,
}],
}),
}
}
fn serper_web_search(input: CallToolRequest) -> Result<CallToolResult, Error> {
let args = input.params.arguments.unwrap_or_default();
let query = match args.get("q") {
Some(Value::String(q)) => q.clone(),
_ => {
return Ok(CallToolResult {
is_error: Some(true),
content: vec![Content {
annotations: None,
text: Some("Please provide a 'q' argument for the search query".into()),
mime_type: None,
r#type: ContentType::Text,
data: None,
}],
});
}
};
let api_key = config::get("SERPER_API_KEY")?
.ok_or_else(|| Error::msg("SERPER_API_KEY configuration is required but not set"))?;
let mut headers = BTreeMap::new();
headers.insert("X-API-KEY".to_string(), api_key);
headers.insert("Content-Type".to_string(), "application/json".to_string());
let req = HttpRequest {
url: "https://google.serper.dev/search".to_string(),
headers,
method: Some("POST".to_string()),
};
let body = json!({ "q": query });
let res = http::request(&req, Some(&body.to_string()))?;
let response_body = res.body();
let response_text = String::from_utf8_lossy(response_body.as_slice()).to_string();
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(response_text),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult{
tools: vec![
ToolDescription {
name: "serper_web_search".into(),
description: "Performs a Google web search using the Serper API and returns the raw JSON response for the given query string.".into(),
input_schema: json!({
"type": "object",
"properties": {
"q": {
"type": "string",
"description": "The search query string",
},
},
"required": ["q"],
})
.as_object()
.unwrap()
.clone(),
},
],
})
}
```
--------------------------------------------------------------------------------
/tests/fixtures/skip_tools_examples.yaml:
--------------------------------------------------------------------------------
```yaml
# Test configuration file demonstrating various skip_tools functionality
plugins:
# Plugin with exact tool name matches
exact_match_plugin:
url: "file:///path/to/exact_plugin"
runtime_config:
skip_tools:
- "debug_tool"
- "test_runner"
- "deprecated_helper"
# Plugin with wildcard patterns
wildcard_plugin:
url: "https://example.com/wildcard_plugin"
runtime_config:
skip_tools:
- "temp_.*" # Skip any tool starting with "temp_"
- ".*_backup" # Skip any tool ending with "_backup"
- "debug.*" # Skip any tool starting with "debug"
# Plugin with complex regex patterns
regex_plugin:
url: "http://localhost:3000/regex_plugin"
runtime_config:
skip_tools:
- "tool_[0-9]+" # Skip tools like "tool_1", "tool_42"
- "test_(unit|integration)" # Skip "test_unit" and "test_integration"
- "[a-z]+_helper" # Skip lowercase word followed by "_helper"
# Plugin with anchored patterns (already have anchors)
anchored_plugin:
url: "file:///path/to/anchored_plugin"
runtime_config:
skip_tools:
- "^system_.*" # Explicit start anchor
- ".*_internal$" # Explicit end anchor
- "^exact_only$" # Fully anchored
# Plugin with case-sensitive patterns
case_sensitive_plugin:
url: "https://api.example.com/case_plugin"
runtime_config:
skip_tools:
- "Tool" # Only matches "Tool", not "tool" or "TOOL"
- "DEBUG_.*" # Only matches uppercase DEBUG prefix
- "CamelCase.*" # Matches tools starting with "CamelCase"
# Plugin with special regex characters escaped
special_chars_plugin:
url: "file:///path/to/special_plugin"
runtime_config:
skip_tools:
- "file\\.exe" # Matches "file.exe" literally
- "script\\?" # Matches "script?" literally
- "temp\\*data" # Matches "temp*data" literally
- "path\\\\tool" # Matches "path\tool" literally
# Plugin with empty skip_tools (skip nothing)
empty_skip_plugin:
url: "https://example.com/empty_plugin"
runtime_config:
skip_tools: []
allowed_hosts:
- "example.com"
# Plugin with no skip_tools specified (defaults to None)
no_skip_plugin:
url: "file:///path/to/no_skip_plugin"
runtime_config:
allowed_hosts:
- "localhost"
memory_limit: "512MB"
# Plugin demonstrating mixed runtime config with skip_tools
full_config_plugin:
url: "https://secure.example.com/full_plugin"
runtime_config:
skip_tools:
- "admin_.*"
- ".*_dangerous"
- "system_critical"
allowed_hosts:
- "api.example.com"
- "cdn.example.com"
allowed_paths:
- "/tmp"
- "/var/app/data"
env_vars:
ENVIRONMENT: "test"
LOG_LEVEL: "debug"
memory_limit: "2GB"
# Plugin with common tool patterns to skip
common_patterns_plugin:
url: "file:///path/to/common_plugin"
runtime_config:
skip_tools:
- ".*_test" # Skip all test tools
- "dev_.*" # Skip all development tools
- "mock_.*" # Skip all mock tools
- "stub_.*" # Skip all stub tools
- ".*_deprecated" # Skip all deprecated tools
```
--------------------------------------------------------------------------------
/CREATING_PLUGINS.md:
--------------------------------------------------------------------------------
```markdown
# Creating Plugins
> **📌 Recommended: Use Plugin Templates**
>
> The fastest and easiest way to create a plugin is to use the provided templates. Templates include all necessary boilerplate, build configuration, and documentation out of the box.
>
> **[👉 Start with the Plugin Templates](./templates/plugins/README.md)**
Check out our [example plugins](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins/v2) for insight.
> Note: Prior versions of hyper-mcp used a different plugin interface (v1). While this plugin interface is still supported, new plugins should use the v2 interface.
## Quick Start with Templates
The recommended way to create a new plugin:
1. **Browse available templates** in [`templates/plugins/`](./templates/plugins/README.md)
2. **Copy the template** for your language:
```sh
cp -r templates/plugins/rust/ ../my-plugin/
cd ../my-plugin/
```
3. **Follow the template README** - each template includes comprehensive setup instructions, examples, and best practices
4. **Customize and implement** your plugin logic
5. **Build and publish** using the provided `Dockerfile`
See [Plugin Templates Documentation](./templates/plugins/README.md) for complete details and language options.
## Using XTP (Alternative Method)
If you prefer to use the XTP CLI tool:
1. Install the [XTP CLI](https://docs.xtp.dylibso.com/docs/cli):
```sh
curl https://static.dylibso.com/cli/install.sh -s | bash
```
2. Create a new plugin project:
```sh
xtp plugin init --schema-file xtp-plugin-schema.yaml
```
Follow the prompts to set up your plugin. This will create the necessary files and structure.
For example, if you chose Rust as the language, it will create a `Cargo.toml`, `src/lib.rs` and a `src/pdk.rs` file.
3. Implement your plugin logic in the language appropriate files(s) created (e.g. - `Cargo.toml` and `src/lib.rs` for Rust)
For example, if you chose Rust as the language you will need to update the `Cargo.toml` and `src/lib.rs` files.
Be sure to modify the `.gitignore` that is created for you to allow committing your `Cargo.lock` file.
## Publishing Plugins
### Rust
To publish a Rust plugin:
```dockerfile
# example how to build with rust
FROM rust:1.88-slim AS builder
RUN rustup target add wasm32-wasip1 && \
rustup component add rust-std --target wasm32-wasip1 && \
cargo install cargo-auditable
WORKDIR /workspace
COPY . .
RUN cargo fetch
RUN cargo auditable build --release --target wasm32-wasip1
FROM scratch
WORKDIR /
COPY --from=builder /workspace/target/wasm32-wasip1/release/plugin.wasm /plugin.wasm
```
Then build and push:
```sh
docker build -t your-registry/plugin-name .
docker push your-registry/plugin-name
```
**Note:** The Rust template includes this Dockerfile and all necessary build configuration - no additional setup needed if you're using the template.
## Next Steps
- **[📖 Plugin Templates Documentation](./templates/plugins/README.md)** - Comprehensive guide to using templates
- **[🚀 Rust Plugin Template](./templates/plugins/rust/README.md)** - Complete Rust plugin setup and development guide
- **[📚 Example Plugins](https://github.com/tuananh/hyper-mcp/tree/main/examples/plugins)** - Working examples to learn from
- **[🔗 MCP Protocol Specification](https://spec.modelcontextprotocol.io/)** - Protocol details and specifications
- **[⚙️ Extism Documentation](https://docs.extism.org/)** - Plugin runtime and PDK documentation
```