#
tokens: 48704/50000 10/231 files (page 4/8)
lines: off (toggle) GitHub
raw markdown copy
This is page 4 of 8. Use http://codebase.md/tuananh/hyper-mcp?lines=false&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/files.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/url"

	"github.com/extism/go-pdk"
)

var (
	GetFileContentsTool = ToolDescription{
		Name:        "gh-get-file-contents",
		Description: "Get the contents of a file or a directory in a GitHub repository",
		InputSchema: schema{
			"type": "object",
			"properties": props{
				"owner":  prop("string", "The owner of the repository"),
				"repo":   prop("string", "The repository name"),
				"path":   prop("string", "The path of the file"),
				"branch": prop("string", "(optional string): Branch to get contents from"),
			},
			"required": []string{"owner", "repo", "path"},
		},
	}
	CreateOrUpdateFileTool = ToolDescription{
		Name:        "gh-create-or-update-file",
		Description: "Create or update a file in a GitHub repository",
		InputSchema: schema{
			"type": "object",
			"properties": props{
				"owner":   prop("string", "The owner of the repository"),
				"repo":    prop("string", "The repository name"),
				"path":    prop("string", "The path of the file"),
				"content": prop("string", "The content of the file"),
				"message": prop("string", "The commit message"),
				"branch":  prop("string", "The branch name"),
				"sha":     prop("string", "(optional) The sha of the file, required for updates"),
			},
			"required": []string{"owner", "repo", "path", "content", "message", "branch"},
		},
	}
	PushFilesTool = ToolDescription{
		Name:        "gh-push-files",
		Description: "Push files to a GitHub repository",
		InputSchema: schema{
			"type": "object",
			"properties": props{
				"owner":   prop("string", "The owner of the repository"),
				"repo":    prop("string", "The repository name"),
				"branch":  prop("string", "The branch name to push to"),
				"message": prop("string", "The commit message"),
				"files": SchemaProperty{
					Type:        "array",
					Description: "Array of files to push",
					Items: &schema{
						"type": "object",
						"properties": props{
							"path":    prop("string", "The path of the file"),
							"content": prop("string", "The content of the file"),
						},
					},
				},
			},
		},
	}
	FileTools = []ToolDescription{
		GetFileContentsTool,
		CreateOrUpdateFileTool,
		PushFilesTool,
	}
)

type FileCreate struct {
	Content string  `json:"content"`
	Message string  `json:"message"`
	Branch  string  `json:"branch"`
	Sha     *string `json:"sha,omitempty"`
}

func fileCreateFromArgs(args map[string]interface{}) FileCreate {
	file := FileCreate{}
	if content, ok := args["content"].(string); ok {
		b64c := base64.StdEncoding.EncodeToString([]byte(content))
		file.Content = b64c
	}
	if message, ok := args["message"].(string); ok {
		file.Message = message
	}
	if branch, ok := args["branch"].(string); ok {
		file.Branch = branch
	}
	if sha, ok := args["sha"].(string); ok {
		file.Sha = some(sha)
	}
	return file
}

func filesCreateOrUpdate(apiKey string, owner string, repo string, path string, file FileCreate) (CallToolResult, error) {
	if file.Sha == nil {
		uc, err := filesGetContentsInternal(apiKey, owner, repo, path, &file.Branch)
		if err != nil {
			pdk.Log(pdk.LogDebug, "File does not exist, creating it")
		} else if !uc.isArray {
			sha := uc.FileContent.Sha
			file.Sha = &sha
		}
	}

	url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/contents/", path)
	req := pdk.NewHTTPRequest(pdk.MethodPut, url)
	req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
	req.SetHeader("Accept", "application/vnd.github.v3+json")
	req.SetHeader("User-Agent", "github-mcpx-servlet")

	res, err := json.Marshal(file)
	if err != nil {
		return CallToolResult{
			IsError: some(true),
			Content: []Content{{
				Type: ContentTypeText,
				Text: some(fmt.Sprint("Failed to marshal file: ", err)),
			}},
		}, nil
	}

	req.SetBody([]byte(res))
	resp := req.Send()
	if resp.Status() != 201 {
		return CallToolResult{
			IsError: some(true),
			Content: []Content{{
				Type: ContentTypeText,
				Text: some(fmt.Sprint("Failed to create or update file: ", resp.Status(), " ", string(resp.Body()))),
			}},
		}, nil
	}

	return CallToolResult{
		Content: []Content{{
			Type: ContentTypeText,
			Text: some(string(resp.Body())),
		}},
	}, nil

}

type UnionContent struct {
	isArray           bool
	FileContent       FileContent
	DirectoryContents []DirectoryContent
}

type FileContent struct {
	Type        string `json:"type"`
	Encoding    string `json:"encoding"`
	Size        int    `json:"size"`
	Name        string `json:"name"`
	Path        string `json:"path"`
	Content     string `json:"content"`
	Sha         string `json:"sha"`
	Url         string `json:"url"`
	GitUrl      string `json:"git_url"`
	HtmlUrl     string `json:"html_url"`
	DownloadUrl string `json:"download_url"`
}

type DirectoryContent struct {
	Type        string  `json:"type"`
	Size        int     `json:"size"`
	Name        string  `json:"name"`
	Path        string  `json:"path"`
	Sha         string  `json:"sha"`
	Url         string  `json:"url"`
	GitUrl      string  `json:"git_url"`
	HtmlUrl     string  `json:"html_url"`
	DownloadUrl *string `json:"download_url"`
}

func filesGetContents(apiKey string, owner string, repo string, path string, branch *string) CallToolResult {
	res, err := filesGetContentsInternal(apiKey, owner, repo, path, branch)
	if err == nil {
		var v []byte
		if res.isArray {
			v, err = json.Marshal(res.DirectoryContents)
		} else {
			v, err = json.Marshal(res.FileContent)
		}
		if err == nil {
			return CallToolResult{
				Content: []Content{{
					Type: ContentTypeText,
					Text: some(string(v)),
				}},
			}
		}
	}
	return CallToolResult{
		IsError: some(true),
		Content: []Content{{
			Type: ContentTypeText,
			Text: some(err.Error()),
		}},
	}
}

func filesGetContentsInternal(apiKey string, owner string, repo string, path string, branch *string) (UnionContent, error) {
	u := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/contents/", path)

	params := url.Values{}
	if branch != nil {
		params.Add("ref", *branch)
	}
	u = fmt.Sprint(u, "?", params.Encode())

	req := pdk.NewHTTPRequest(pdk.MethodGet, u)
	req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
	req.SetHeader("Accept", "application/vnd.github.v3+json")
	req.SetHeader("User-Agent", "github-mcpx-servlet")

	resp := req.Send()
	if resp.Status() != 200 {
		return UnionContent{}, fmt.Errorf("Failed to get file contents: %d %s (%s)", resp.Status(), string(resp.Body()), u)
	}

	// attempt to parse this as a file
	uc := UnionContent{}
	fc := &uc.FileContent
	if err := json.Unmarshal(resp.Body(), fc); err == nil {
		base64.StdEncoding.DecodeString(fc.Content)
		// replace it with the decoded content
		fc.Content = string(fc.Content)
		return uc, nil
	} else {
		// if it's not a file, try to parse it as a directory
		d := []DirectoryContent{}
		if err := json.Unmarshal(resp.Body(), &d); err != nil {
			return UnionContent{}, fmt.Errorf("Failed to unmarshal directory contents: %w", err)
		}
		uc.DirectoryContents = d
		uc.isArray = true
		return uc, nil
	}
}

type FileOperation struct {
	Path    string `json:"path"`
	Content string `json:"content"`
}

func filePushFromArgs(args map[string]interface{}) []FileOperation {
	files := []FileOperation{}
	if f, ok := args["files"].([]interface{}); ok {
		for _, file := range f {
			if file, ok := file.(map[string]interface{}); ok {
				files = append(files, FileOperation{
					Path:    file["path"].(string),
					Content: file["content"].(string),
				})
			}
		}
	}
	return files
}

func filesPush(apiKey, owner, repo, branch, message string, files []FileOperation) CallToolResult {
	url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/git/refs/heads/", branch)
	req := pdk.NewHTTPRequest(pdk.MethodGet, url)
	req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
	req.SetHeader("Accept", "application/vnd.github.v3+json")
	req.SetHeader("User-Agent", "github-mcpx-servlet")

	resp := req.Send()
	if resp.Status() != 200 {
		return CallToolResult{
			IsError: some(true),
			Content: []Content{{
				Type: ContentTypeText,
				Text: some(fmt.Sprint("Failed to get branch: ", resp.Status())),
			}},
		}
	}

	ref := RefSchema{}
	json.Unmarshal(resp.Body(), &ref)

	commitSha := ref.Object.Sha
	if tree, err := createTree(apiKey, owner, repo, files, commitSha); err != nil {
		return CallToolResult{
			IsError: some(true),
			Content: []Content{{
				Type: ContentTypeText,
				Text: some(fmt.Sprint("Failed to create tree: ", err)),
			}},
		}
	} else if commit, err := createCommit(apiKey, owner, repo, message, tree.Sha, []string{commitSha}); err != nil {
		return CallToolResult{
			IsError: some(true),
			Content: []Content{{
				Type: ContentTypeText,
				Text: some(fmt.Sprint("Failed to create commit: ", err)),
			}},
		}
	} else {
		return updateRef(apiKey, owner, repo, "heads/"+branch, commit.Sha)
	}
}

type TreeSchema struct {
	BaseTree  string      `json:"base_tree,omitempty"`
	Tree      []TreeEntry `json:"tree"`
	Truncated bool        `json:"truncated,omitempty"`
	Url       string      `json:"url,omitempty"`
	Sha       string      `json:"sha,omitempty"`
}
type TreeEntry struct {
	Path    string `json:"path"`
	Mode    string `json:"mode"`
	Type    string `json:"type"`
	Content string `json:"content,omitempty"`
	Size    int    `json:"size,omitempty"`
	Sha     string `json:"sha,omitempty"`
	Url     string `json:"url,omitempty"`
}

func createTree(apiKey, owner, repo string, files []FileOperation, baseTree string) (TreeSchema, error) {
	tree := TreeSchema{
		BaseTree: baseTree,
		Tree:     []TreeEntry{},
	}

	for _, file := range files {
		tree.Tree = append(tree.Tree, TreeEntry{
			Path: file.Path, Mode: "100644", Type: "blob", Content: file.Content})
	}

	url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/git/trees")
	req := pdk.NewHTTPRequest(pdk.MethodPost, url)
	req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
	req.SetHeader("Accept", "application/vnd.github.v3+json")
	req.SetHeader("User-Agent", "github-mcpx-servlet")
	req.SetHeader("Content-Type", "application/json")

	res, err := json.Marshal(tree)

	if err != nil {
		return TreeSchema{}, fmt.Errorf("Failed to marshal tree: %w", err)
	}
	req.SetBody(res)

	resp := req.Send()
	if resp.Status() != 201 {
		return TreeSchema{}, fmt.Errorf("Failed to create tree: %d %s", resp.Status(), string(resp.Body()))
	}

	ts := TreeSchema{}
	err = json.Unmarshal(resp.Body(), &res)
	return ts, err
}

type Author struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Date  string `json:"date"`
}

type Commit struct {
	Sha       string `json:"sha"`
	NodeID    string `json:"node_id"`
	Url       string `json:"url"`
	Author    Author `json:"author"`
	Committer Author `json:"committer"`
	Message   string `json:"message"`
	Tree      []struct {
		Sha string `json:"sha"`
		Url string `json:"url"`
	} `json:"tree"`
	Parents []struct {
		Sha string `json:"sha"`
		Url string `json:"url"`
	} `json:"parents"`
}

func createCommit(apiKey, owner, repo, message, tree string, parents []string) (Commit, error) {
	commit := map[string]interface{}{
		"message": message,
		"tree":    tree,
		"parents": parents,
	}

	url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/git/commits")
	req := pdk.NewHTTPRequest(pdk.MethodPost, url)
	req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
	req.SetHeader("Accept", "application/vnd.github.v3+json")
	req.SetHeader("User-Agent", "github-mcpx-servlet")
	req.SetHeader("Content-Type", "application/json")

	res, _ := json.Marshal(commit)
	req.SetBody(res)

	resp := req.Send()
	if resp.Status() != 201 {
		return Commit{}, fmt.Errorf("Failed to create commit: %d %s", resp.Status(), string(resp.Body()))
	}

	cs := Commit{}
	json.Unmarshal(resp.Body(), &cs)
	return cs, nil
}

func updateRef(apiKey, owner, repo, ref, sha string) CallToolResult {
	url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/git/refs/", ref)
	req := pdk.NewHTTPRequest(pdk.MethodPatch, url)
	req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
	req.SetHeader("Accept", "application/vnd.github.v3+json")
	req.SetHeader("User-Agent", "github-mcpx-servlet")
	req.SetHeader("Content-Type", "application/json")

	res, _ := json.Marshal(map[string]any{"sha": sha, "force": true})
	req.SetBody(res)

	resp := req.Send()
	if resp.Status() != 200 {
		return CallToolResult{
			IsError: some(true),
			Content: []Content{{
				Type: ContentTypeText,
				Text: some(fmt.Sprint("Failed to update ref: ", resp.Status(), " ", string(resp.Body()))),
			}},
		}
	}

	return CallToolResult{
		Content: []Content{{
			Type: ContentTypeText,
			Text: some(string(resp.Body())),
		}},
	}
}

```

--------------------------------------------------------------------------------
/src/wasm/oci.rs:
--------------------------------------------------------------------------------

```rust
use crate::config::{OciConfig, PluginName};
use anyhow::{Result, anyhow};
use docker_credential::{CredentialRetrievalError, DockerCredential};
use flate2::read::GzDecoder;
use oci_client::{
    Client, Reference, client::ClientConfig, manifest, manifest::OciDescriptor,
    secrets::RegistryAuth,
};
use sha2::{Digest, Sha256};
use sigstore::{
    cosign::{
        ClientBuilder, CosignCapabilities,
        verification_constraint::{
            CertSubjectEmailVerifier, CertSubjectUrlVerifier, VerificationConstraintVec,
            cert_subject_email_verifier::StringVerifier,
        },
        verify_constraints,
    },
    errors::SigstoreVerifyConstraintsError,
    registry::{Auth, OciReference},
    trust::{ManualTrustRoot, TrustRoot, sigstore::SigstoreTrustRoot},
};
use std::{fs, io::Read, path::Path, str::FromStr};
use tar::Archive;
use tokio::sync::OnceCell;
use url::Url;

static OCI_CLIENT: OnceCell<Client> = OnceCell::const_new();

fn build_auth(reference: &Reference) -> RegistryAuth {
    let server = reference
        .resolve_registry()
        .strip_suffix('/')
        .unwrap_or_else(|| reference.resolve_registry());

    // if cli.anonymous {
    //     return RegistryAuth::Anonymous;
    // }

    match docker_credential::get_credential(server) {
        Err(CredentialRetrievalError::ConfigNotFound) => RegistryAuth::Anonymous,
        Err(CredentialRetrievalError::NoCredentialConfigured) => RegistryAuth::Anonymous,
        Err(e) => {
            tracing::info!("Error retrieving docker credentials: {e}. Using anonymous auth");
            RegistryAuth::Anonymous
        }
        Ok(DockerCredential::UsernamePassword(username, password)) => {
            tracing::info!("Found docker credentials");
            RegistryAuth::Basic(username, password)
        }
        Ok(DockerCredential::IdentityToken(_)) => {
            tracing::info!(
                "Cannot use contents of docker config, identity token not supported. Using anonymous auth"
            );
            RegistryAuth::Anonymous
        }
    }
}

pub async fn load_wasm(url: &Url, config: &OciConfig, plugin_name: &PluginName) -> Result<Vec<u8>> {
    let image_reference = url.as_str().strip_prefix("oci://").unwrap();
    let target_file_path = "/plugin.wasm";
    let mut hasher = Sha256::new();
    hasher.update(image_reference);
    let hash = hasher.finalize();
    let short_hash = &hex::encode(hash)[..7];
    let cache_dir = dirs::cache_dir()
        .map(|mut path| {
            path.push("hyper-mcp");
            path
        })
        .unwrap();
    std::fs::create_dir_all(&cache_dir)?;

    let local_output_path = cache_dir.join(format!("{plugin_name}-{short_hash}.wasm"));
    let local_output_path = local_output_path.to_str().unwrap();

    if let Err(e) =
        pull_and_extract_oci_image(config, image_reference, target_file_path, local_output_path)
            .await
    {
        tracing::error!("Error pulling oci plugin: {e}");
        return Err(anyhow::anyhow!("Failed to pull OCI plugin: {e}"));
    }
    tracing::info!("cache plugin `{plugin_name}` to : {local_output_path}");
    tokio::fs::read(local_output_path)
        .await
        .map_err(|e| e.into())
}

async fn setup_trust_repository(config: &OciConfig) -> Result<Box<dyn TrustRoot>> {
    if config.use_sigstore_tuf_data {
        // Use Sigstore TUF data from the official repository
        tracing::info!("Using Sigstore TUF data for verification");
        match SigstoreTrustRoot::new(None).await {
            Ok(repo) => return Ok(Box::new(repo)),
            Err(e) => {
                tracing::error!("Failed to initialize TUF trust repository: {e}");
                if !config.insecure_skip_signature {
                    return Err(anyhow!(
                        "Failed to initialize TUF trust repository and signature verification is required"
                    ));
                }
                tracing::info!("Falling back to manual trust repository");
            }
        }
    }

    // Create a manual trust repository
    let mut data = ManualTrustRoot::default();

    // Add Rekor public keys if provided
    if let Some(rekor_keys_path) = &config.rekor_pub_keys {
        if rekor_keys_path.exists() {
            match fs::read(rekor_keys_path) {
                Ok(content) => {
                    tracing::info!("Added Rekor public key");
                    if let Some(path_str) = rekor_keys_path.to_str() {
                        data.rekor_keys.insert(path_str.to_string(), content);
                        tracing::info!("Added Rekor public key from: {}", path_str);
                    }
                }
                Err(e) => tracing::warn!("Failed to read Rekor public keys file: {e}"),
            }
        } else {
            tracing::warn!("Rekor public keys file not found: {rekor_keys_path:?}");
        }
    }

    // Add Fulcio certificates if provided
    if let Some(fulcio_certs_path) = &config.fulcio_certs {
        if fulcio_certs_path.exists() {
            match fs::read(fulcio_certs_path) {
                Ok(content) => {
                    let certificate = sigstore::registry::Certificate {
                        encoding: sigstore::registry::CertificateEncoding::Pem,
                        data: content,
                    };

                    match certificate.try_into() {
                        Ok(cert) => {
                            tracing::info!("Added Fulcio certificate");
                            data.fulcio_certs.push(cert);
                        }
                        Err(e) => tracing::warn!("Failed to parse Fulcio certificate: {e}"),
                    }
                }
                Err(e) => tracing::warn!("Failed to read Fulcio certificates file: {e}"),
            }
        } else {
            tracing::warn!("Fulcio certificates file not found: {fulcio_certs_path:?}");
        }
    }

    Ok(Box::new(data))
}

async fn verify_image_signature(config: &OciConfig, image_reference: &str) -> Result<bool> {
    tracing::info!("Verifying signature for {image_reference}");

    // Set up the trust repository based on CLI arguments
    let repo = setup_trust_repository(config).await?;
    let auth = &Auth::Anonymous;

    // Create a client builder
    let client_builder = ClientBuilder::default();

    // Create client with trust repository
    let client_builder = match client_builder.with_trust_repository(repo.as_ref()) {
        Ok(builder) => builder,
        Err(e) => return Err(anyhow!("Failed to set up trust repository: {e}")),
    };

    // Build the client
    let mut client = match client_builder.build() {
        Ok(client) => client,
        Err(e) => return Err(anyhow!("Failed to build Sigstore client: {e}")),
    };

    // Parse the reference
    let image_ref = match OciReference::from_str(image_reference) {
        Ok(reference) => reference,
        Err(e) => return Err(anyhow!("Invalid image reference: {e}")),
    };

    // Triangulate to find the signature image and source digest
    let (cosign_signature_image, source_image_digest) =
        match client.triangulate(&image_ref, auth).await {
            Ok((sig_image, digest)) => (sig_image, digest),
            Err(e) => {
                tracing::warn!("Failed to triangulate image: {e}");
                return Ok(false); // No signatures found
            }
        };

    // Get trusted signature layers
    let signature_layers = match client
        .trusted_signature_layers(auth, &source_image_digest, &cosign_signature_image)
        .await
    {
        Ok(layers) => layers,
        Err(e) => {
            tracing::warn!("Failed to get trusted signature layers: {e}");
            return Ok(false);
        }
    };

    if signature_layers.is_empty() {
        tracing::warn!("No valid signatures found for {image_reference}");
        return Ok(false);
    }

    // Build verification constraints based on CLI options
    let mut verification_constraints: VerificationConstraintVec = Vec::new();

    if let Some(cert_email) = &config.cert_email {
        let issuer = config
            .cert_issuer
            .as_ref()
            .map(|i| StringVerifier::ExactMatch(i.to_string()));

        verification_constraints.push(Box::new(CertSubjectEmailVerifier {
            email: StringVerifier::ExactMatch(cert_email.to_string()),
            issuer,
        }));
    }

    if let Some(cert_url) = &config.cert_url {
        match config.cert_issuer.as_ref() {
            Some(issuer) => {
                verification_constraints.push(Box::new(CertSubjectUrlVerifier {
                    url: cert_url.to_string(),
                    issuer: issuer.to_string(),
                }));
            }
            None => {
                tracing::warn!("'cert-issuer' is required when 'cert-url' is specified");
            }
        }
    }

    // Verify the constraints
    match verify_constraints(&signature_layers, verification_constraints.iter()) {
        Ok(()) => {
            tracing::info!("Signature verification successful for {image_reference}");
            Ok(true)
        }
        Err(SigstoreVerifyConstraintsError {
            unsatisfied_constraints,
        }) => {
            tracing::warn!(
                "Signature verification failed for {image_reference}: {unsatisfied_constraints:?}"
            );
            Ok(false)
        }
    }
}

async fn pull_and_extract_oci_image(
    config: &OciConfig,
    image_reference: &str,
    target_file_path: &str,
    local_output_path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    if Path::new(local_output_path).exists() {
        tracing::info!(
            "Plugin {image_reference} already cached at: {local_output_path}. Skipping downloading."
        );
        return Ok(());
    }

    tracing::info!("Pulling {image_reference} ...");

    let reference = Reference::try_from(image_reference)?;
    let auth = build_auth(&reference);

    // Verify the image signature if it's an OCI image and verification is enabled
    if !config.insecure_skip_signature {
        tracing::info!("Signature verification enabled for {image_reference}");
        match verify_image_signature(config, image_reference).await {
            Ok(verified) => {
                if !verified {
                    return Err(format!(
                        "No valid signatures found for the image {image_reference}"
                    )
                    .into());
                }
            }
            Err(e) => {
                return Err(format!("Image signature verification failed: {e}").into());
            }
        }
    } else {
        tracing::warn!("Signature verification disabled for {image_reference}");
    }

    let client = OCI_CLIENT
        .get_or_init(|| async { Client::new(ClientConfig::default()) })
        .await;

    // Accept both OCI and Docker manifest types
    let manifest = client
        .pull(
            &reference,
            &auth,
            vec![
                manifest::IMAGE_MANIFEST_MEDIA_TYPE,
                manifest::IMAGE_DOCKER_LAYER_GZIP_MEDIA_TYPE,
                manifest::IMAGE_LAYER_GZIP_MEDIA_TYPE,
            ],
        )
        .await?;

    for layer in manifest.layers.iter() {
        let mut buf = Vec::new();
        let desc = OciDescriptor {
            digest: layer.sha256_digest().clone(),
            media_type: "application/vnd.docker.image.rootfs.diff.tar.gzip".to_string(),
            ..Default::default()
        };
        client.pull_blob(&reference, &desc, &mut buf).await.unwrap();

        let gz_extract = GzDecoder::new(&buf[..]);
        let mut archive_extract = Archive::new(gz_extract);

        for entry_result in archive_extract.entries()? {
            match entry_result {
                Ok(mut entry) => {
                    if let Ok(path) = entry.path() {
                        let path_str = path.to_string_lossy();
                        if path_str.ends_with(target_file_path) || path_str.ends_with("plugin.wasm")
                        {
                            if let Some(parent) = Path::new(local_output_path).parent() {
                                fs::create_dir_all(parent)?;
                            }
                            let mut content = Vec::new();
                            entry.read_to_end(&mut content)?;
                            fs::write(local_output_path, content)?;
                            tracing::info!("Successfully extracted to: {local_output_path}");
                            return Ok(());
                        }
                    }
                }
                Err(e) => tracing::info!("Error during extraction: {e}"),
            }
        }
    }

    Err("Target file not found in any layer".into())
}

```

--------------------------------------------------------------------------------
/src/plugin.rs:
--------------------------------------------------------------------------------

```rust
use crate::config::PluginName;
use async_trait::async_trait;
use rmcp::{
    ErrorData as McpError,
    model::*,
    service::{NotificationContext, RequestContext, RoleServer},
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Value, json};
use std::{
    fmt::Debug,
    ops::Deref,
    sync::{Arc, Mutex},
};
use tokio_util::sync::CancellationToken;

type PluginHandle = Arc<Mutex<extism::Plugin>>;

#[derive(Clone, Debug, Serialize, Deserialize)]
struct PluginRequestContext {
    pub id: NumberOrString,
    #[serde(rename = "_meta")]
    pub meta: Meta,
}

impl<'a> From<&'a RequestContext<RoleServer>> for PluginRequestContext {
    fn from(context: &'a RequestContext<RoleServer>) -> Self {
        PluginRequestContext {
            id: context.id.clone(),
            meta: context.meta.clone(),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
struct PluginNotificationContext {
    #[serde(rename = "_meta")]
    pub meta: Meta,
}

impl<'a> From<&'a NotificationContext<RoleServer>> for PluginNotificationContext {
    fn from(context: &'a NotificationContext<RoleServer>) -> Self {
        PluginNotificationContext {
            meta: context.meta.clone(),
        }
    }
}

#[async_trait]
#[allow(unused_variables)]
pub trait Plugin: Send + Sync + Debug {
    async fn call_tool(
        &self,
        request: CallToolRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<CallToolResult, McpError>;

    async fn complete(
        &self,
        request: CompleteRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<CompleteResult, McpError> {
        Ok(CompleteResult::default())
    }

    async fn get_prompt(
        &self,
        request: GetPromptRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<GetPromptResult, McpError> {
        Err(McpError::method_not_found::<GetPromptRequestMethod>())
    }

    async fn list_prompts(
        &self,
        request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListPromptsResult, McpError> {
        Ok(ListPromptsResult::default())
    }

    async fn list_resources(
        &self,
        request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListResourcesResult, McpError> {
        Ok(ListResourcesResult::default())
    }

    async fn list_resource_templates(
        &self,
        request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListResourceTemplatesResult, McpError> {
        Ok(ListResourceTemplatesResult::default())
    }

    async fn list_tools(
        &self,
        request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListToolsResult, McpError>;

    fn name(&self) -> &PluginName;

    async fn on_roots_list_changed(
        &self,
        context: NotificationContext<RoleServer>,
    ) -> Result<(), McpError> {
        Ok(())
    }

    fn plugin(&self) -> &PluginHandle;

    async fn read_resource(
        &self,
        request: ReadResourceRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<ReadResourceResult, McpError> {
        Err(McpError::method_not_found::<ReadResourceRequestMethod>())
    }
}

async fn call_plugin<R>(
    plugin: &dyn Plugin,
    name: &str,
    payload: String,
    ct: CancellationToken,
) -> Result<R, McpError>
where
    R: DeserializeOwned + Send + 'static,
{
    let plugin_name = plugin.name().to_string();
    if !function_exists_plugin(plugin, name) {
        return Err(McpError::invalid_request(
            format!("Method {name} not found for plugin {plugin_name}"),
            None,
        ));
    }
    let plugin = Arc::clone(plugin.plugin());
    let cancel_handle = {
        let guard = plugin.lock().unwrap();
        guard.cancel_handle()
    };

    let name = name.to_string();
    let mut join = tokio::task::spawn_blocking(move || {
        let mut plugin = plugin.lock().unwrap();
        let result: Result<String, extism::Error> = plugin.call(&name, payload);
        match result {
            Ok(res) => match serde_json::from_str::<R>(&res) {
                Ok(parsed) => Ok(parsed),
                Err(e) => Err(McpError::internal_error(
                    format!("Failed to deserialize data: {e}"),
                    None,
                )),
            },
            Err(e) => Err(McpError::internal_error(
                format!("Failed to call plugin: {e}"),
                None,
            )),
        }
    });

    tokio::select! {
        // Finished normally
        res = &mut join => {
            match res {
                Ok(Ok(result)) => Ok(result),
                Ok(Err(e)) => Err(e),
                Err(e) => Err(McpError::internal_error(
                    format!("Failed to spawn blocking task for plugin {plugin_name}: {e}"),
                    None,
                )),
            }
        }

        //Cancellation requested
        _ = ct.cancelled() => {
            if let Err(e) = cancel_handle.cancel() {
                tracing::error!("Failed to cancel plugin {plugin_name}: {e}");
                return Err(McpError::internal_error(
                    format!("Failed to cancel plugin {plugin_name}: {e}"),
                    None,
                ));
            }
            match tokio::time::timeout(std::time::Duration::from_millis(250), join).await {
                Ok(Ok(Ok(_))) => Err(McpError::internal_error(
                    format!("Plugin {plugin_name} was cancelled"),
                    None,
                )),
                Ok(Ok(Err(e))) => Err(McpError::internal_error(
                    format!("Failed to execute plugin {plugin_name}: {e}"),
                    None,
                )),
                Ok(Err(e)) => Err(McpError::internal_error(
                    format!("Join error for plugin {plugin_name}: {e}"),
                    None,
                )),
                Err(_) => Err(McpError::internal_error(
                    format!("Timeout waiting for plugin {plugin_name} to cancel"),
                    None,
                )),
            }
        }
    }
}

fn function_exists_plugin(plugin: &dyn Plugin, name: &str) -> bool {
    let plugin = Arc::clone(plugin.plugin());
    plugin.lock().unwrap().function_exists(name)
}

async fn notify_plugin(plugin: &dyn Plugin, name: &str, payload: String) -> Result<(), McpError> {
    let plugin_name = plugin.name().to_string();
    if !function_exists_plugin(plugin, name) {
        return Err(McpError::invalid_request(
            format!("Method {name} not found for plugin {plugin_name}"),
            None,
        ));
    }
    let plugin = Arc::clone(plugin.plugin());
    let name = name.to_string();
    tokio::task::spawn_blocking(move || {
        let mut plugin = plugin.lock().unwrap();
        let result: Result<String, extism::Error> = plugin.call(&name, payload);
        if let Err(e) = result {
            tracing::error!("Failed to notify plugin {plugin_name}: {e}");
        }
    });
    Ok(())
}

#[derive(Debug)]
pub struct PluginBase {
    pub name: PluginName,
    pub plugin: PluginHandle,
}

#[derive(Debug)]
pub struct PluginV1(pub PluginBase);

impl Deref for PluginV1 {
    type Target = PluginBase;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[async_trait]
impl Plugin for PluginV1 {
    async fn call_tool(
        &self,
        request: CallToolRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<CallToolResult, McpError> {
        call_plugin::<CallToolResult>(
            self,
            "call",
            serde_json::to_string(&json!({
                "params": request,
            }))
            .expect("Failed to serialize request"),
            context.ct,
        )
        .await
    }

    async fn list_tools(
        &self,
        _request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListToolsResult, McpError> {
        call_plugin::<ListToolsResult>(self, "describe", "".to_string(), context.ct).await
    }

    fn name(&self) -> &PluginName {
        &self.name
    }

    fn plugin(&self) -> &PluginHandle {
        &self.plugin
    }
}

impl PluginV1 {
    pub fn new(name: PluginName, plugin: PluginHandle) -> Self {
        Self(PluginBase { name, plugin })
    }
}

#[derive(Debug)]
pub struct PluginV2(pub PluginBase);

impl Deref for PluginV2 {
    type Target = PluginBase;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[async_trait]
impl Plugin for PluginV2 {
    async fn call_tool(
        &self,
        request: CallToolRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<CallToolResult, McpError> {
        call_plugin::<CallToolResult>(
            self,
            "call_tool",
            serde_json::to_string(&json!({
                "request": request,
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize request"),
            context.ct,
        )
        .await
    }

    async fn complete(
        &self,
        request: CompleteRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<CompleteResult, McpError> {
        #[derive(Debug, Clone)]
        struct Helper(CompleteRequestParam);

        impl Serialize for Helper {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                let mut value = serde_json::to_value(&self.0).map_err(serde::ser::Error::custom)?;

                if let Value::Object(root) = &mut value
                    && let Some(Value::Object(ref_obj)) = root.get_mut("ref")
                    && let Some(Value::String(t)) = ref_obj.get_mut("type")
                    && let Some(stripped) = t.strip_prefix("ref/")
                {
                    *t = stripped.to_string();
                }

                value.serialize(serializer)
            }
        }

        call_plugin::<CompleteResult>(
            self,
            "complete",
            serde_json::to_string(&json!({
                "request": Helper(request),
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize request"),
            context.ct,
        )
        .await
    }

    async fn get_prompt(
        &self,
        request: GetPromptRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<GetPromptResult, McpError> {
        call_plugin::<GetPromptResult>(
            self,
            "get_prompt",
            serde_json::to_string(&json!({
                "request": request,
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize request"),
            context.ct,
        )
        .await
    }

    async fn list_prompts(
        &self,
        _request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListPromptsResult, McpError> {
        if !function_exists_plugin(self, "list_prompts") {
            return Ok(ListPromptsResult::default());
        }
        call_plugin::<ListPromptsResult>(
            self,
            "list_prompts",
            serde_json::to_string(&json!({
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize context"),
            context.ct,
        )
        .await
    }

    async fn list_resources(
        &self,
        _request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListResourcesResult, McpError> {
        if !function_exists_plugin(self, "list_resources") {
            return Ok(ListResourcesResult::default());
        }
        call_plugin::<ListResourcesResult>(
            self,
            "list_resources",
            serde_json::to_string(&json!({
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize context"),
            context.ct,
        )
        .await
    }

    async fn list_resource_templates(
        &self,
        _request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListResourceTemplatesResult, McpError> {
        if !function_exists_plugin(self, "list_resource_templates") {
            return Ok(ListResourceTemplatesResult::default());
        }
        call_plugin::<ListResourceTemplatesResult>(
            self,
            "list_resource_templates",
            serde_json::to_string(&json!({
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize context"),
            context.ct,
        )
        .await
    }

    async fn list_tools(
        &self,
        _request: Option<PaginatedRequestParam>,
        context: RequestContext<RoleServer>,
    ) -> Result<ListToolsResult, McpError> {
        if !function_exists_plugin(self, "list_tools") {
            return Ok(ListToolsResult::default());
        }
        call_plugin::<ListToolsResult>(
            self,
            "list_tools",
            serde_json::to_string(&json!({
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize context"),
            context.ct,
        )
        .await
    }

    fn name(&self) -> &PluginName {
        &self.name
    }

    async fn on_roots_list_changed(
        &self,
        context: NotificationContext<RoleServer>,
    ) -> Result<(), McpError> {
        if function_exists_plugin(self, "on_roots_list_changed") {
            return Ok(());
        }
        notify_plugin(
            self,
            "on_roots_list_changed",
            serde_json::to_string(&json!({
                "context": PluginNotificationContext::from(&context),
            }))
            .expect("Failed to serialize context"),
        )
        .await
    }

    fn plugin(&self) -> &PluginHandle {
        &self.plugin
    }

    async fn read_resource(
        &self,
        request: ReadResourceRequestParam,
        context: RequestContext<RoleServer>,
    ) -> Result<ReadResourceResult, McpError> {
        call_plugin::<ReadResourceResult>(
            self,
            "read_resource",
            serde_json::to_string(&json!({
                "request": request,
                "context": PluginRequestContext::from(&context),
            }))
            .expect("Failed to serialize request"),
            context.ct,
        )
        .await
    }
}

impl PluginV2 {
    pub fn new(name: PluginName, plugin: PluginHandle) -> Self {
        Self(PluginBase { name, plugin })
    }
}

```

--------------------------------------------------------------------------------
/examples/plugins/v1/context7/src/lib.rs:
--------------------------------------------------------------------------------

```rust
mod pdk;

use extism_pdk::*;
use pdk::types::{
    CallToolRequest, CallToolResult, Content, ContentType, ListToolsResult, ToolDescription,
};
use serde_json::{Value as JsonValue, json};
use urlencoding::encode;

const CONTEXT7_API_BASE_URL: &str = "https://context7.com/api"; // Guessed API base URL

pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
    match input.params.name.as_str() {
        "c7_resolve_library_id" => c7_resolve_library_id(input),
        "c7_get_library_docs" => c7_get_library_docs(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 c7_resolve_library_id(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.unwrap_or_default();
    let library_name_val = args.get("library_name").unwrap_or(&JsonValue::Null);

    if let JsonValue::String(library_name_as_query) = library_name_val {
        let encoded_query = encode(library_name_as_query);
        let url = format!(
            "{}/v1/search?query={}",
            CONTEXT7_API_BASE_URL, encoded_query
        );

        let mut req = HttpRequest::new(&url).with_method("GET");
        req.headers
            .insert("X-Context7-Source".to_string(), "mcp-server".to_string());

        match http::request::<()>(&req, None) {
            Ok(res) => {
                let body_str = String::from_utf8_lossy(&res.body()).to_string();
                if res.status_code() >= 200 && res.status_code() < 300 {
                    match serde_json::from_str::<JsonValue>(&body_str) {
                        Ok(parsed_json) => {
                            let mut results_text_parts = Vec::new();

                            // Check if the root is an object and has a "results" field which is an array
                            if let Some(results_node) = parsed_json.get("results") {
                                if let JsonValue::Array(results_array) = results_node {
                                    if results_array.is_empty() {
                                        results_text_parts.push(
                                            "No libraries found matching your query.".to_string(),
                                        );
                                    } else {
                                        for result_item in results_array {
                                            let mut item_details = Vec::new();

                                            let title = result_item
                                                .get("title")
                                                .and_then(JsonValue::as_str)
                                                .unwrap_or("N/A");
                                            item_details.push(format!("- Title: {}", title));

                                            let id = result_item
                                                .get("id")
                                                .and_then(JsonValue::as_str)
                                                .unwrap_or("N/A");
                                            item_details.push(format!(
                                                "- Context7-compatible library ID: {}",
                                                id
                                            ));

                                            let description = result_item
                                                .get("description")
                                                .and_then(JsonValue::as_str)
                                                .unwrap_or("N/A");
                                            item_details
                                                .push(format!("- Description: {}", description));

                                            if let Some(v) = result_item
                                                .get("totalSnippets")
                                                .and_then(JsonValue::as_i64)
                                                .filter(|&v| v >= 0)
                                            {
                                                item_details.push(format!("- Code Snippets: {}", v))
                                            }

                                            if let Some(v) = result_item
                                                .get("stars")
                                                .and_then(JsonValue::as_i64)
                                                .filter(|&v| v >= 0)
                                            {
                                                item_details.push(format!("- GitHub Stars: {}", v))
                                            }

                                            results_text_parts.push(item_details.join("\n"));
                                        }
                                    }
                                } else {
                                    results_text_parts.push("API response 'results' field was not an array as expected.".to_string());
                                }
                            } else {
                                results_text_parts.push(
                                    "API response did not contain a 'results' field as expected."
                                        .to_string(),
                                );
                            }

                            let header = "Available Libraries (top matches):\n\nEach result includes information like:\n- Title: Library or package name\n- Context7-compatible library ID: Identifier (format: /org/repo)\n- Description: Short summary\n- Code Snippets: Number of available code examples (if available)\n- GitHub Stars: Popularity indicator (if available)\n\nFor best results, select libraries based on name match, popularity (stars), snippet coverage, and relevance to your use case.\n\n---\n";
                            let final_text =
                                format!("{}{}", header, results_text_parts.join("\n\n"));

                            Ok(CallToolResult {
                                is_error: None,
                                content: vec![Content {
                                    annotations: None,
                                    text: Some(final_text),
                                    mime_type: Some("text/markdown".to_string()),
                                    r#type: ContentType::Text,
                                    data: None,
                                }],
                            })
                        }
                        Err(e) => {
                            // Failed to parse the JSON body
                            Ok(CallToolResult {
                                is_error: Some(true),
                                content: vec![Content {
                                    annotations: None,
                                    text: Some(format!(
                                        "Failed to parse API response JSON: {}. Body: {}",
                                        e, body_str
                                    )),
                                    mime_type: None,
                                    r#type: ContentType::Text,
                                    data: None,
                                }],
                            })
                        }
                    }
                } else {
                    Ok(CallToolResult {
                        is_error: Some(true),
                        content: vec![Content {
                            annotations: None,
                            text: Some(format!(
                                "API request failed with status {}: {}",
                                res.status_code(),
                                body_str
                            )),
                            mime_type: None,
                            r#type: ContentType::Text,
                            data: None,
                        }],
                    })
                }
            }
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("HTTP request failed: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some(
                    "Missing required parameter: library_name (or not a string)".to_string(),
                ),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn c7_get_library_docs(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.unwrap_or_default();
    let library_id_json_val = args
        .get("context7_compatible_library_id")
        .unwrap_or(&JsonValue::Null);

    if let JsonValue::String(original_id_str) = library_id_json_val {
        let mut id_for_path = original_id_str.clone();
        let mut folders_value_opt: Option<String> = None;

        if let Some(idx) = original_id_str.rfind("?folders=") {
            let (id_part, folders_part_with_query) = original_id_str.split_at(idx);
            id_for_path = id_part.to_string();
            folders_value_opt = Some(
                folders_part_with_query
                    .trim_start_matches("?folders=")
                    .to_string(),
            );
        }

        let mut query_params_vec = vec![format!(
            "context7CompatibleLibraryID={}",
            encode(original_id_str) // Use the original, full ID string for this query parameter
        )];

        if let Some(folders_val) = &folders_value_opt {
            if !folders_val.is_empty() {
                query_params_vec.push(format!("folders={}", encode(folders_val)));
            }
        }

        if let Some(JsonValue::String(topic_str)) = args.get("topic") {
            if !topic_str.is_empty() {
                // Ensure topic is not empty before adding
                query_params_vec.push(format!("topic={}", encode(topic_str)));
            }
        }

        if let Some(JsonValue::Number(tokens_num_json)) = args.get("tokens") {
            if let Some(tokens_f64) = tokens_num_json.as_f64() {
                query_params_vec.push(format!("tokens={}", tokens_f64 as i64));
            } else if let Some(tokens_i64) = tokens_num_json.as_i64() {
                query_params_vec.push(format!("tokens={}", tokens_i64));
            }
        }

        let final_id_for_path_segment = id_for_path.strip_prefix("/").unwrap_or(&id_for_path);

        let query_params = query_params_vec.join("&");
        let url = format!(
            "{}/v1/{}/?{}", // Corrected URL: ensure '?' before query parameters
            CONTEXT7_API_BASE_URL, final_id_for_path_segment, query_params
        );

        let mut req = HttpRequest::new(&url).with_method("GET");
        req.headers
            .insert("X-Context7-Source".to_string(), "mcp-server".to_string());

        match http::request::<()>(&req, None) {
            Ok(res) => {
                let body_str = String::from_utf8_lossy(&res.body()).to_string();
                if res.status_code() >= 200 && res.status_code() < 300 {
                    // Directly use the body_str as markdown content
                    Ok(CallToolResult {
                        is_error: None,
                        content: vec![Content {
                            annotations: None,
                            text: Some(body_str),
                            mime_type: Some("text/markdown".to_string()), // Assuming it's still markdown
                            r#type: ContentType::Text,
                            data: None,
                        }],
                    })
                } else {
                    Ok(CallToolResult {
                        is_error: Some(true),
                        content: vec![Content {
                            annotations: None,
                            text: Some(format!(
                                "API request for docs (URL: {}) failed with status {}: {}",
                                url,
                                res.status_code(),
                                body_str
                            )),
                            mime_type: None,
                            r#type: ContentType::Text,
                            data: None,
                        }],
                    })
                }
            }
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("HTTP request for docs failed: {}, URL: {}", e, url)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some(
                    "Missing required parameter: context7_compatible_library_id (or not a string)"
                        .to_string(),
                ),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

pub(crate) fn describe() -> Result<ListToolsResult, Error> {
    Ok(ListToolsResult {
        tools: vec![
            ToolDescription {
                name: "c7_resolve_library_id".into(),
                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.".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "library_name": {
                            "type": "string",
                            "description": "Library name to search for and retrieve a Context7-compatible library ID.",
                        },
                    },
                    "required": ["library_name"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "c7_get_library_docs".into(),
                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.".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "context7_compatible_library_id": {
                            "type": "string",
                            "description": "Exact Context7-compatible library ID (e.g., 'mongodb/docs', 'vercel/nextjs') retrieved from 'c7_resolve_library_id'.",
                        },
                        "topic": {
                            "type": "string",
                            "description": "Topic to focus documentation on (e.g., 'hooks', 'routing').",
                        },
                        "tokens": {
                            "type": "integer",
                            "description": "Maximum number of tokens of documentation to retrieve (default: 10000). Higher values provide more context but consume more tokens.",
                        },
                    },
                    "required": ["context7_compatible_library_id"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
        ],
    })
}

```

--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/src/qdrant_client.rs:
--------------------------------------------------------------------------------

```rust
use anyhow::{Error, anyhow, bail};
use extism_pdk::*;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_json::{Map, Value};
use std::collections::BTreeMap;
use std::fmt::Display;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PointId {
    Uuid(String),
    Num(u64),
}
impl From<u64> for PointId {
    fn from(num: u64) -> Self {
        PointId::Num(num)
    }
}
impl From<String> for PointId {
    fn from(uuid: String) -> Self {
        PointId::Uuid(uuid)
    }
}
impl Display for PointId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PointId::Uuid(uuid) => write!(f, "{}", uuid),
            PointId::Num(num) => write!(f, "{}", num),
        }
    }
}

/// The point struct.
/// A point is a record consisting of a vector and an optional payload.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Point {
    /// Id of the point
    pub id: PointId,

    /// Vectors
    pub vector: Vec<f32>,

    /// Additional information along with vectors
    pub payload: Option<Map<String, Value>>,
}

/// The point struct with the score returned by searching
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScoredPoint {
    /// Id of the point
    pub id: PointId,

    /// Vectors
    pub vector: Option<Vec<f32>>,

    /// Additional information along with vectors
    pub payload: Option<Map<String, Value>>,

    /// Points vector distance to the query vector
    pub score: f32,
}

pub struct QdrantClient {
    url_base: String,
    api_key: Option<String>,
}

impl QdrantClient {
    pub fn new_with_url(url_base_: String) -> QdrantClient {
        QdrantClient {
            url_base: url_base_,
            api_key: None,
        }
    }

    pub fn new() -> QdrantClient {
        QdrantClient::new_with_url("http://localhost:6333".to_string())
    }

    pub fn set_api_key(&mut self, api_key: impl Into<String>) {
        self.api_key = Some(api_key.into());
    }
}

impl Default for QdrantClient {
    fn default() -> Self {
        Self::new()
    }
}

/// Shortcut functions
impl QdrantClient {
    /// Shortcut functions
    pub fn collection_info(&self, collection_name: &str) -> Result<u64, Error> {
        let v = self.collection_info_api(collection_name)?;
        v.get("result")
            .and_then(|v| v.get("points_count"))
            .and_then(|v| v.as_u64())
            .ok_or_else(|| anyhow!("[qdrant] Invalid response format"))
    }

    pub fn create_collection(&self, collection_name: &str, size: u32) -> Result<(), Error> {
        match self.collection_exists(collection_name)? {
            false => (),
            true => {
                let err_msg = format!("Collection '{}' already exists", collection_name);
                bail!(err_msg);
            }
        }

        let params = json!({
            "vectors": {
                "size": size,
                "distance": "Cosine",
                "on_disk": true,
            }
        });
        if !self.create_collection_api(collection_name, &params)? {
            bail!("Failed to create collection '{}'", collection_name);
        }
        Ok(())
    }

    pub fn list_collections(&self) -> Result<Vec<String>, Error> {
        self.list_collections_api()
    }

    pub fn collection_exists(&self, collection_name: &str) -> Result<bool, Error> {
        let collection_names = self.list_collections()?;
        Ok(collection_names.contains(&collection_name.to_string()))
    }

    pub fn delete_collection(&self, collection_name: &str) -> Result<(), Error> {
        match self.collection_exists(collection_name)? {
            true => (),
            false => {
                let err_msg = format!("Not found collection '{}'", collection_name);
                bail!(err_msg);
            }
        }

        if !self.delete_collection_api(collection_name)? {
            bail!("Failed to delete collection '{}'", collection_name);
        }
        Ok(())
    }

    pub fn upsert_points(&self, collection_name: &str, points: Vec<Point>) -> Result<(), Error> {
        let params = json!({
            "points": points,
        });
        self.upsert_points_api(collection_name, &params)
    }

    pub fn search_points(
        &self,
        collection_name: &str,
        vector: Vec<f32>,
        limit: u64,
        score_threshold: Option<f32>,
    ) -> Result<Vec<ScoredPoint>, Error> {
        let score_threshold = score_threshold.unwrap_or(0.0);

        let params = json!({
            "vector": vector,
            "limit": limit,
            "with_payload": true,
            "with_vector": true,
            "score_threshold": score_threshold,
        });

        match self.search_points_api(collection_name, &params) {
            Ok(v) => match v.get("result") {
                Some(v) => match v.as_array() {
                    Some(rs) => {
                        let mut sps: Vec<ScoredPoint> = Vec::<ScoredPoint>::new();
                        for r in rs {
                            let sp: ScoredPoint = serde_json::from_value(r.clone())?;
                            sps.push(sp);
                        }
                        Ok(sps)
                    }
                    None => {
                        bail!(
                            "[qdrant] The value corresponding to the 'result' key is not an array."
                        )
                    }
                },
                None => Ok(vec![]),
            },
            Err(_) => Ok(vec![]),
        }
    }

    pub fn get_points(&self, collection_name: &str, ids: &[PointId]) -> Result<Vec<Point>, Error> {
        let params = json!({
            "ids": ids,
            "with_payload": true,
            "with_vector": true,
        });

        let v = self.get_points_api(collection_name, &params)?;
        let rs = v
            .get("result")
            .and_then(|v| v.as_array())
            .ok_or_else(|| anyhow!("[qdrant] Invalid response format"))?;

        let mut ps: Vec<Point> = Vec::new();
        for r in rs {
            let p: Point = serde_json::from_value(r.clone())?;
            ps.push(p);
        }
        Ok(ps)
    }

    pub fn get_point(&self, collection_name: &str, id: &PointId) -> Result<Point, Error> {
        let v = self.get_point_api(collection_name, id)?;
        let r = v
            .get("result")
            .ok_or_else(|| anyhow!("[qdrant] Invalid response format"))?;
        Ok(serde_json::from_value(r.clone())?)
    }

    pub fn delete_points(&self, collection_name: &str, ids: &[PointId]) -> Result<(), Error> {
        let params = json!({
            "points": ids,
        });
        self.delete_points_api(collection_name, &params)
    }

    /// REST API functions
    pub fn collection_info_api(&self, collection_name: &str) -> Result<Value, Error> {
        let url = format!("{}/collections/{}", self.url_base, collection_name);

        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let response: HttpResponse = http::request::<()>(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("GET".to_string()),
            },
            None,
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        Ok(json)
    }

    pub fn create_collection_api(
        &self,
        collection_name: &str,
        params: &Value,
    ) -> Result<bool, Error> {
        let url = format!("{}/collections/{}", self.url_base, collection_name);
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let body = serde_json::to_vec(params)?;
        let response = http::request::<Vec<u8>>(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("PUT".to_string()),
            },
            Some(body),
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        let success = json
            .get("result")
            .and_then(|v| v.as_bool())
            .ok_or_else(|| anyhow!("[qdrant] Invalid response format"))?;
        Ok(success)
    }

    pub fn list_collections_api(&self) -> Result<Vec<String>, Error> {
        let url = format!("{}/collections", self.url_base);
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let response = http::request::<()>(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("GET".to_string()),
            },
            None,
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;

        match json.get("result") {
            Some(result) => match result.get("collections") {
                Some(collections) => match collections.as_array() {
                    Some(collections) => {
                        let mut collection_names = Vec::new();
                        for collection in collections {
                            if let Some(name) = collection.get("name").and_then(|n| n.as_str()) {
                                collection_names.push(name.to_string());
                            }
                        }
                        Ok(collection_names)
                    }
                    None => bail!(
                        "[qdrant] The value corresponding to the 'collections' key is not an array."
                    ),
                },
                None => bail!("[qdrant] The given key 'collections' does not exist."),
            },
            None => bail!("[qdrant] The given key 'result' does not exist."),
        }
    }

    pub fn collection_exists_api(&self, collection_name: &str) -> Result<bool, Error> {
        let url = format!("{}/collections/{}/exists", self.url_base, collection_name);
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let response = http::request::<()>(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("GET".to_string()),
            },
            None,
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        match json.get("result") {
            Some(result) => {
                let exists = result
                    .get("exists")
                    .and_then(|v| v.as_bool())
                    .ok_or_else(|| anyhow!("[qdrant] Invalid response format"))?;
                Ok(exists)
            }
            None => Err(anyhow!("[qdrant] Failed to check collection existence")),
        }
    }

    pub fn delete_collection_api(&self, collection_name: &str) -> Result<bool, Error> {
        let url = format!("{}/collections/{}", self.url_base, collection_name);
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let response = http::request::<()>(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("DELETE".to_string()),
            },
            None,
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        let success = json
            .get("result")
            .and_then(|v| v.as_bool())
            .ok_or_else(|| anyhow!("[qdrant] Invalid response format"))?;
        Ok(success)
    }

    pub fn upsert_points_api(&self, collection_name: &str, params: &Value) -> Result<(), Error> {
        let url = format!(
            "{}/collections/{}/points?wait=true",
            self.url_base, collection_name,
        );
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let body = serde_json::to_vec(params)?;
        let response = http::request(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("PUT".to_string()),
            },
            Some(&body),
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        let status = json
            .get("status")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow!("[qdrant] Invalid response format"))?;

        if status == "ok" {
            Ok(())
        } else {
            Err(anyhow!(
                "[qdrant] Failed to upsert points. Status = {}",
                status
            ))
        }
    }

    pub fn search_points_api(&self, collection_name: &str, params: &Value) -> Result<Value, Error> {
        let url = format!(
            "{}/collections/{}/points/search",
            self.url_base, collection_name,
        );
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let body = serde_json::to_vec(params)?;
        let response = http::request(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("POST".to_string()),
            },
            Some(&body),
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        Ok(json)
    }

    pub fn get_points_api(&self, collection_name: &str, params: &Value) -> Result<Value, Error> {
        let url = format!("{}/collections/{}/points", self.url_base, collection_name);
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let body = serde_json::to_vec(params)?;
        let response = http::request(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("POST".to_string()),
            },
            Some(&body),
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        Ok(json)
    }

    pub fn get_point_api(&self, collection_name: &str, id: &PointId) -> Result<Value, Error> {
        let url = format!(
            "{}/collections/{}/points/{}",
            self.url_base, collection_name, id,
        );
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let response = http::request::<()>(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("GET".to_string()),
            },
            None,
        )?;

        let json: Value = serde_json::from_slice(&response.body())?;
        Ok(json)
    }

    pub fn delete_points_api(&self, collection_name: &str, params: &Value) -> Result<(), Error> {
        let url = format!(
            "{}/collections/{}/points/delete?wait=true",
            self.url_base, collection_name,
        );
        let mut headers = BTreeMap::new();
        headers.insert("Content-Type".to_string(), "application/json".to_string());
        if let Some(api_key) = &self.api_key {
            headers.insert("api-key".to_string(), api_key.clone());
        }

        let body = serde_json::to_vec(params)?;
        let response = http::request(
            &HttpRequest {
                url: url.clone(),
                headers,
                method: Some("POST".to_string()),
            },
            Some(&body),
        )?;

        Ok(())
    }
}

```

--------------------------------------------------------------------------------
/RUNTIME_CONFIG.md:
--------------------------------------------------------------------------------

```markdown
# Runtime Configuration

## Structure

The configuration is structured as follows:

- **auths** (`object`, optional): Authentication configurations for HTTPS requests, keyed by URL.
- **plugins**: A map of plugin names to  plugin configuration objects.
  - **path** (`string`): OCI path or HTTP URL or local path for the plugin.
  - **runtime_config** (`object`, optional): Plugin-specific runtime configuration. The available fields are:
    - **skip_tools** (`array[string]`, optional): List of regex patterns for tool names to skip loading at runtime. Each pattern is automatically anchored to match the entire tool name (equivalent to wrapping with `^` and `$`). Supports full regex syntax for powerful pattern matching.
    - **allowed_hosts** (`array[string]`, optional): List of allowed hosts for the plugin (e.g., `["1.1.1.1"]` or `["*"]`).
    - **allowed_paths** (`array[string]`, optional): List of allowed file system paths.
    - **env_vars** (`object`, optional): Key-value pairs of environment variables for the plugin.
    - **memory_limit** (`string`, optional): Memory limit for the plugin (e.g., `"512Mi"`).

## Plugin Names

Plugin names must follow strict naming conventions to ensure consistency and avoid conflicts:

### Allowed Characters
- **Letters**: A-Z, a-z (case-sensitive)
- **Numbers**: 0-9
- **Underscores**: _ (as separators only)

### Naming Rules
- Must start with a letter or number (not underscore)
- Must end with a letter or number (not underscore)
- Cannot contain consecutive underscores
- Cannot contain hyphens or other special characters
- Cannot contain spaces or whitespace

### Valid Examples
```
✅ plugin
✅ myPlugin
✅ plugin_name
✅ plugin123
✅ my_awesome_plugin_v2
✅ Plugin_Name_123
```

### Invalid Examples
```
❌ plugin-name        (hyphens not allowed)
❌ plugin_            (cannot end with underscore)
❌ _plugin            (cannot start with underscore)
❌ plugin__name       (consecutive underscores)
❌ plugin name        (spaces not allowed)
❌ plugin@name        (special characters not allowed)
```

### Best Practices
- Use descriptive, meaningful names
- Follow consistent naming conventions within your organization
- Consider using prefixes for related plugins (e.g., `company_auth`, `company_logging`)
- Use underscores to separate logical components (e.g., `api_client`, `data_processor`)

## Authentication Configuration

The `auths` field allows you to configure authentication for HTTPS requests made by plugins. Authentication is matched by URL prefix, with longer prefixes taking precedence.

### Supported Authentication Types

#### Basic Authentication
```yaml
auths:
  "https://api.example.com":
    type: basic
    username: "your-username"
    password: "your-password"
```

#### Bearer Token Authentication
```yaml
auths:
  "https://api.example.com":
    type: token
    token: "your-bearer-token"
```

#### Keyring Authentication
```yaml
auths:
  "https://private.registry.io":
    type: keyring
    service: "my-app"
    user: "registry-user"
```

### Keyring Setup Examples

For keyring authentication, you need to store the actual auth configuration JSON in your system keyring. This provides secure credential storage without exposing sensitive data in config files.

#### macOS (using Keychain Access or security command)

**Using the `security` command:**
```bash
# Store basic auth credentials
security add-generic-password -a "registry-user" -s "my-app" -w '{"type":"basic","username":"actual-user","password":"actual-pass"}'

# Store token auth credentials
security add-generic-password -a "api-user" -s "my-service" -w '{"type":"token","token":"actual-bearer-token"}'

# Verify the entry was created
security find-generic-password -a "registry-user" -s "my-app"
```

**Using Keychain Access GUI:**
1. Open Keychain Access (Applications → Utilities → Keychain Access)
2. Click "File" → "New Password Item"
3. Set "Keychain Item Name" to your service name (e.g., "my-app")
4. Set "Account Name" to your user name (e.g., "registry-user")
5. Set "Password" to the JSON auth config: `{"type":"basic","username":"actual-user","password":"actual-pass"}`
6. Click "Add"

#### Linux (using libsecret/gnome-keyring)

**Install required tools:**
```bash
# Ubuntu/Debian
sudo apt-get install libsecret-tools

# RHEL/CentOS/Fedora
sudo yum install libsecret-devel
```

**Using `secret-tool`:**
```bash
# Store basic auth credentials
echo '{"type":"basic","username":"actual-user","password":"actual-pass"}' | secret-tool store --label="my-app credentials" service "my-app" username "registry-user"

# Store token auth credentials
echo '{"type":"token","token":"actual-bearer-token"}' | secret-tool store --label="my-service token" service "my-service" username "api-user"

# Verify the entry was created
secret-tool lookup service "my-app" username "registry-user"
```

#### Windows (using Windows Credential Manager)

**Using `cmdkey` (Command Prompt as Administrator):**
```cmd
REM Store basic auth credentials (escape quotes for JSON)
cmdkey /generic:"my-app" /user:"registry-user" /pass:"{\"type\":\"basic\",\"username\":\"actual-user\",\"password\":\"actual-pass\"}"

REM Store token auth credentials
cmdkey /generic:"my-service" /user:"api-user" /pass:"{\"type\":\"token\",\"token\":\"actual-bearer-token\"}"

REM Verify the entry was created
cmdkey /list:"my-app"
```

**Using Credential Manager GUI:**
1. Open "Credential Manager" from Control Panel → User Accounts → Credential Manager
2. Click "Add a generic credential"
3. Set "Internet or network address" to your service name (e.g., "my-app")
4. Set "User name" to your user name (e.g., "registry-user")
5. Set "Password" to the JSON auth config: `{"type":"basic","username":"actual-user","password":"actual-pass"}`
6. Click "OK"

**Using PowerShell:**
```powershell
# Store basic auth credentials
$cred = New-Object System.Management.Automation.PSCredential("registry-user", (ConvertTo-SecureString '{"type":"basic","username":"actual-user","password":"actual-pass"}' -AsPlainText -Force))
New-StoredCredential -Target "my-app" -Credential $cred -Type Generic
```

### URL Matching Behavior

Authentication is applied based on URL prefix matching:
- Longer prefixes take precedence over shorter ones
- Exact matches take highest precedence
- URLs are matched case-sensitively

**Example:**
```yaml
auths:
  "https://example.com":
    type: basic
    username: "broad-user"
    password: "broad-pass"
  "https://example.com/api":
    type: token
    token: "api-token"
  "https://example.com/api/v1":
    type: basic
    username: "v1-user"
    password: "v1-pass"
```

- Request to `https://example.com/api/v1/users` → uses v1 basic auth (longest match)
- Request to `https://example.com/api/data` → uses api token auth
- Request to `https://example.com/public` → uses broad basic auth

### Keyring Authentication Example

**Configuration file:**
```yaml
auths:
  "https://private.registry.io":
    type: keyring
    service: "private-registry"
    user: "registry-user"
  "https://internal.company.com":
    type: keyring
    service: "company-api"
    user: "api-user"

plugins:
  secure-plugin:
    url: "https://private.registry.io/secure-plugin"
    runtime_config:
      allowed_hosts:
        - "private.registry.io"
```

**Corresponding keyring entries (stored separately):**
- Service: `private-registry`, User: `registry-user`, Password: `{"type":"basic","username":"real-user","password":"real-pass"}`
- Service: `company-api`, User: `api-user`, Password: `{"type":"token","token":"company-jwt-token"}`

### Real-World Keyring Scenarios

#### Scenario 1: Corporate Environment
```yaml
auths:
  "https://artifactory.company.com":
    type: keyring
    service: "company-artifactory"
    user: "build-service"
  "https://nexus.company.com":
    type: keyring
    service: "company-nexus"
    user: "deployment-bot"
```

Setup corporate credentials once:
```bash
# macOS
security add-generic-password -a "build-service" -s "company-artifactory" -w '{"type":"basic","username":"corp_user","password":"corp_secret"}'

# Linux
echo '{"type":"basic","username":"corp_user","password":"corp_secret"}' | secret-tool store --label="Company Artifactory" service "company-artifactory" username "build-service"

# Windows
cmdkey /generic:"company-artifactory" /user:"build-service" /pass:"{\"type\":\"basic\",\"username\":\"corp_user\",\"password\":\"corp_secret\"}"
```

#### Scenario 2: Multi-Environment Setup
```yaml
auths:
  "https://staging-api.example.com":
    type: keyring
    service: "example-staging"
    user: "staging-user"
  "https://prod-api.example.com":
    type: keyring
    service: "example-prod"
    user: "prod-user"
```

Store different credentials for each environment:
```bash
# Staging credentials
security add-generic-password -a "staging-user" -s "example-staging" -w '{"type":"token","token":"staging-jwt-token"}'

# Production credentials
security add-generic-password -a "prod-user" -s "example-prod" -w '{"type":"token","token":"prod-jwt-token"}'
```

#### Scenario 3: Team Shared Configuration
```yaml
# Team members can share this config file safely
auths:
  "https://shared-registry.team.com":
    type: keyring
    service: "team-registry"
    user: "developer"
```

Each team member stores their own credentials:
```bash
# Developer A
security add-generic-password -a "developer" -s "team-registry" -w '{"type":"basic","username":"alice","password":"alice_key"}'

# Developer B
security add-generic-password -a "developer" -s "team-registry" -w '{"type":"basic","username":"bob","password":"bob_key"}'
```

### Keyring Best Practices

1. **Service Naming Convention**: Use descriptive, consistent service names (e.g., `company-artifactory`, `project-registry`)
2. **User Identification**: Use role-based usernames (e.g., `build-service`, `deployment-bot`) rather than personal names
3. **Credential Rotation**: Update keyring entries when rotating credentials - no config file changes needed
4. **Environment Separation**: Use different service names for different environments
5. **Team Coordination**: Document your service/user naming conventions for team members
6. **Backup Strategy**: Consider backing up keyring entries for critical services
7. **Testing**: Use non-production credentials in keyring for testing

## 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_tool"           # Skip exact tool name
        - "temp_.*"              # Skip tools starting with "temp_"
        - ".*_backup"            # Skip tools ending with "_backup"
        - "test_[0-9]+"          # Skip tools like "test_1", "test_42"
      env_vars:
        FOO: "bar"
      memory_limit: "512Mi"
  private_plugin:
    url: "https://private.registry.io/my-plugin"
    runtime_config:
      allowed_hosts:
        - "private.registry.io"
```

## 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_tool",
          "temp_.*",
          ".*_backup",
          "test_[0-9]+"
        ],
        "env_vars": {"FOO": "bar"},
        "memory_limit": "512Mi"
      }
    },
    "private_plugin": {
      "url": "https://private.registry.io/my-plugin",
      "runtime_config": {
        "allowed_hosts": ["private.registry.io"]
      }
    }
  }
}
```

## Loading Configuration

Configuration is loaded at runtime from a file with `.json`, `.yaml`, `.yml`, or `.toml` extension. The loader will parse the file according to its extension. If the file does not exist or the format is unsupported, an error will be raised.

## Security Considerations

### Credential Storage
- **Basic/Token auth**: Credentials are stored directly in the config file. Ensure proper file permissions (e.g., `chmod 600`).
- **Keyring auth**: Credentials are stored securely in the system keyring. The config file only contains service/user identifiers.

### Best Practices
- Use keyring authentication for production environments
- Rotate credentials regularly
- Use environment-specific config files
- Never commit credentials to version control
- Consider using short-lived tokens when possible

## Troubleshooting Keyring Authentication

### Common Issues

#### "No matching entry found in secure storage"
This error occurs when the keyring entry doesn't exist or can't be accessed.

**Solutions:**
1. Verify the service and user names match exactly between config and keyring
2. Check that the keyring entry exists:
   ```bash
   # macOS
   security find-generic-password -a "your-user" -s "your-service"

   # Linux
   secret-tool lookup service "your-service" username "your-user"

   # Windows
   cmdkey /list:"your-service"
   ```
3. Ensure the current user has permission to access the keyring entry

#### "Failed to parse JSON from keyring"
This error occurs when the stored password isn't valid JSON or doesn't match the expected AuthConfig format.

**Solutions:**
1. Verify the stored password is valid JSON:
   ```bash
   # macOS - retrieve and validate
   security find-generic-password -a "your-user" -s "your-service" -w | jq .
   ```
2. Ensure the JSON matches one of these formats:
   - `{"type":"basic","username":"real-user","password":"real-pass"}`
   - `{"type":"token","token":"real-token"}`

#### Platform-Specific Issues

**macOS:**
- Keychain may be locked - unlock it manually or use `security unlock-keychain`
- Application may not have keychain access permissions

**Linux:**
- GNOME Keyring service may not be running: `systemctl --user status gnome-keyring`
- D-Bus session may not be available in non-graphical environments

**Windows:**
- Credential Manager may require administrator privileges for certain operations
- Windows Credential Manager has size limits for stored passwords

### Debugging Tips

1. **Test keyring access independently:**
   ```bash
   # Create a test entry
   security add-generic-password -a "test-user" -s "test-service" -w '{"type":"token","token":"test"}'

   # Retrieve it
   security find-generic-password -a "test-user" -s "test-service" -w

   # Clean up
   security delete-generic-password -a "test-user" -s "test-service"
   ```

2. **Validate JSON format:**
   ```bash
   echo '{"type":"basic","username":"user","password":"pass"}' | jq .
   ```

3. **Check permissions:**
   ```bash
   # Ensure config file is readable
   ls -la config.yaml

   # Set appropriate permissions
   chmod 600 config.yaml
   ```

## Skip Tools Pattern Matching

The `skip_tools` field supports powerful regex pattern matching for filtering out unwanted tools at runtime.

> 📖 **For comprehensive examples, advanced patterns, and detailed use cases, see [SKIP_TOOLS_GUIDE.md](./SKIP_TOOLS_GUIDE.md)**

### Pattern Behavior
- **Automatic Anchoring**: Patterns are automatically anchored to match the entire tool name (wrapped with `^` and `$`)
- **Regex Support**: Full regex syntax is supported, including wildcards, character classes, and quantifiers
- **Case Sensitive**: Pattern matching is case-sensitive
- **Compilation**: All patterns are compiled into a single optimized regex set for efficient matching

### Pattern Examples

#### Exact Matches
```yaml
skip_tools:
  - "debug_tool"      # Matches only "debug_tool"
  - "test_runner"     # Matches only "test_runner"
```

#### Wildcard Patterns
```yaml
skip_tools:
  - "temp_.*"         # Matches "temp_file", "temp_data", etc.
  - ".*_backup"       # Matches "data_backup", "file_backup", etc.
  - "debug.*"         # Matches "debug", "debugger", "debug_info", etc.
```

#### Advanced Regex Patterns
```yaml
skip_tools:
  - "tool_[0-9]+"                    # Matches "tool_1", "tool_42", etc.
  - "test_(unit|integration)"        # Matches "test_unit" and "test_integration"
  - "[a-z]+_helper"                  # Matches lowercase word + "_helper"
  - "system_(admin|user)_.*"         # Matches tools starting with "system_admin_" or "system_user_"
```

#### Explicit Anchoring
```yaml
skip_tools:
  - "^prefix_.*"      # Explicit start anchor (same as "prefix_.*" due to auto-anchoring)
  - ".*_suffix$"      # Explicit end anchor (same as ".*_suffix" due to auto-anchoring)
  - "^exact_only$"    # Fully explicit anchoring (same as "exact_only")
```

#### Special Characters
```yaml
skip_tools:
  - "file\\.exe"      # Matches "file.exe" literally (escaped dot)
  - "script\\?"       # Matches "script?" literally (escaped question mark)
  - "temp\\*data"     # Matches "temp*data" literally (escaped asterisk)
```

#### Common Use Cases
```yaml
skip_tools:
  - ".*_test"         # Skip all test tools
  - "dev_.*"          # Skip all development tools
  - "mock_.*"         # Skip all mock tools
  - ".*_deprecated"   # Skip all deprecated tools
  - "admin_.*"        # Skip all admin tools
  - "debug.*"         # Skip all debug-related tools
```

### Error Handling
- Invalid regex patterns will cause configuration loading to fail with a descriptive error
- Empty pattern arrays are allowed and will skip no tools
- The `skip_tools` field can be omitted entirely to skip no tools

### Performance Notes
- All patterns are compiled into a single optimized `RegexSet` for O(1) tool name checking
- Pattern compilation happens once at startup, not per tool evaluation
- Large numbers of patterns have minimal runtime performance impact

## Notes

- Fields marked as `optional` can be omitted.
- Plugin authors may extend `runtime_config` with additional fields, but only the above are officially recognized.
- Authentication applies to all HTTPS requests made by plugins, including plugin downloads and runtime API calls.
- URL matching is case-sensitive and based on string prefix matching.
- Keyring authentication requires platform-specific keyring services to be available and accessible.
- Skip tools patterns use full regex syntax with automatic anchoring for precise tool filtering.

```

--------------------------------------------------------------------------------
/src/naming.rs:
--------------------------------------------------------------------------------

```rust
use crate::config::{PluginName, PluginNameParseError};
use anyhow::Result;
use std::fmt;
use std::str::FromStr;
use url::Url;

#[derive(Debug, Clone)]
pub struct NamespacedNameParseError;

impl fmt::Display for NamespacedNameParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Failed to parse name")
    }
}

impl std::error::Error for NamespacedNameParseError {}

impl From<PluginNameParseError> for NamespacedNameParseError {
    fn from(_: PluginNameParseError) -> Self {
        NamespacedNameParseError
    }
}

pub fn create_namespaced_name(plugin_name: &PluginName, name: &str) -> String {
    format!("{plugin_name}-{name}")
}

pub fn create_namespaced_uri(plugin_name: &PluginName, uri: &str) -> Result<String> {
    let mut uri = Url::parse(uri)?;
    uri.set_path(&format!(
        "{}/{}",
        plugin_name.as_str(),
        uri.path().trim_start_matches('/')
    ));
    Ok(uri.to_string())
}

pub fn parse_namespaced_name(namespaced_name: String) -> Result<(PluginName, String)> {
    if let Some((plugin_name, tool_name)) = namespaced_name.split_once("-") {
        return Ok((PluginName::from_str(plugin_name)?, tool_name.to_string()));
    }
    Err(NamespacedNameParseError.into())
}

pub fn parse_namespaced_uri(namespaced_uri: String) -> Result<(PluginName, String)> {
    let mut uri = Url::parse(namespaced_uri.as_str())?;
    let mut segments = uri
        .path_segments()
        .ok_or(url::ParseError::RelativeUrlWithoutBase)?
        .collect::<Vec<&str>>();
    if segments.is_empty() {
        return Err(NamespacedNameParseError.into());
    }
    let plugin_name = PluginName::from_str(segments.remove(0))?;
    uri.set_path(&segments.join("/"));
    Ok((plugin_name, uri.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_tool_name() {
        let plugin_name = PluginName::from_str("example_plugin").unwrap();
        let tool_name = "example_tool";
        let expected = "example_plugin-example_tool";
        assert_eq!(create_namespaced_name(&plugin_name, tool_name), expected);
    }

    #[test]
    fn test_parse_tool_name() {
        let tool_name = "example_plugin-example_tool".to_string();
        let result = parse_namespaced_name(tool_name);
        assert!(result.is_ok());
        let (plugin_name, tool) = result.unwrap();
        assert_eq!(plugin_name.as_str(), "example_plugin");
        assert_eq!(tool, "example_tool");
    }

    #[test]
    fn test_create_tool_name_invalid() {
        let plugin_name = PluginName::from_str("example_plugin").unwrap();
        let tool_name = "invalid-tool";
        let result = create_namespaced_name(&plugin_name, tool_name);
        assert_eq!(result, "example_plugin-invalid-tool");
    }

    #[test]
    fn test_create_namespaced_tool_name_with_special_chars() {
        let plugin_name = PluginName::from_str("test_plugin_123").unwrap();
        let tool_name = "tool_name_with_underscores";
        let result = create_namespaced_name(&plugin_name, tool_name);
        assert_eq!(result, "test_plugin_123-tool_name_with_underscores");
    }

    #[test]
    fn test_create_namespaced_tool_name_empty_tool_name() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let tool_name = "";
        let result = create_namespaced_name(&plugin_name, tool_name);
        assert_eq!(result, "test_plugin-");
    }

    #[test]
    fn test_create_namespaced_tool_name_multiple_hyphens() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let tool_name = "invalid-tool-name";
        let result = create_namespaced_name(&plugin_name, tool_name);
        assert_eq!(result, "test_plugin-invalid-tool-name");
    }

    #[test]
    fn test_parse_namespaced_tool_name_with_special_chars() {
        let tool_name = "plugin_name_123-tool_name_456".to_string();
        let result = parse_namespaced_name(tool_name).unwrap();
        assert_eq!(result.0.as_str(), "plugin_name_123");
        assert_eq!(result.1, "tool_name_456");
    }

    #[test]
    fn test_parse_namespaced_tool_name_no_separator() {
        let tool_name = "invalid_tool_name".to_string();
        let result = parse_namespaced_name(tool_name);
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_namespaced_tool_name_multiple_separators() {
        let tool_name = "plugin-tool-extra".to_string();
        let result = parse_namespaced_name(tool_name).unwrap();
        assert_eq!(result.0.as_str(), "plugin");
        assert_eq!(result.1, "tool-extra");
    }

    #[test]
    fn test_parse_namespaced_tool_name_empty_parts() {
        let tool_name = "-tool".to_string();
        let result = parse_namespaced_name(tool_name);
        // This should still work but with empty plugin name
        if result.is_ok() {
            let (plugin, _) = result.unwrap();
            assert!(plugin.as_str().is_empty());
        }
    }

    #[test]
    fn test_parse_namespaced_tool_name_only_separator() {
        let tool_name = "-".to_string();
        let result = parse_namespaced_name(tool_name);
        // Should result in empty plugin and tool names
        if let Ok((plugin, tool)) = result {
            assert!(plugin.as_str().is_empty());
            assert!(tool.is_empty());
        }
    }

    #[test]
    fn test_parse_namespaced_tool_name_empty_string() {
        let tool_name = "".to_string();
        let result = parse_namespaced_name(tool_name);
        assert!(result.is_err());
    }

    #[test]
    fn test_tool_name_parse_error_display() {
        let error = NamespacedNameParseError;
        assert_eq!(format!("{error}"), "Failed to parse name");
    }

    #[test]
    fn test_tool_name_parse_error_from_plugin_name_error() {
        let plugin_error = PluginNameParseError;
        let tool_error: NamespacedNameParseError = plugin_error.into();
        assert_eq!(format!("{tool_error}"), "Failed to parse name");
    }

    #[test]
    fn test_round_trip_tool_name_operations() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let original_tool = "my_tool";

        let namespaced = create_namespaced_name(&plugin_name, original_tool);
        let (parsed_plugin, parsed_tool) = parse_namespaced_name(namespaced).unwrap();

        assert_eq!(parsed_plugin.as_str(), "test_plugin");
        assert_eq!(parsed_tool, "my_tool");
    }

    #[test]
    fn test_tool_name_with_unicode() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let tool_name = "тест_工具"; // Cyrillic and Chinese characters

        let result = create_namespaced_name(&plugin_name, tool_name);
        assert_eq!(result, "test_plugin-тест_工具");
    }

    #[test]
    fn test_very_long_tool_names() {
        let plugin_name = PluginName::from_str("plugin").unwrap();
        let very_long_tool = "a".repeat(1000);

        let namespaced = create_namespaced_name(&plugin_name, &very_long_tool);

        let (parsed_plugin, parsed_tool) = parse_namespaced_name(namespaced).unwrap();

        assert_eq!(parsed_plugin.as_str(), "plugin");
        assert_eq!(parsed_tool.len(), 1000);
    }

    #[test]
    fn test_plugin_name_error_conversion() {
        let plugin_error = PluginNameParseError;
        let tool_error: NamespacedNameParseError = plugin_error.into();

        // Test that the error implements standard error traits
        assert!(std::error::Error::source(&tool_error).is_none());
        assert!(!format!("{tool_error}").is_empty());
    }

    #[test]
    fn test_tool_name_with_numbers_and_special_chars() {
        let plugin_name = PluginName::from_str("plugin_123").unwrap();
        let tool_name = "tool_456_test";

        let result = create_namespaced_name(&plugin_name, tool_name);
        assert_eq!(result, "plugin_123-tool_456_test");

        let (parsed_plugin, parsed_tool) = parse_namespaced_name(result).unwrap();
        assert_eq!(parsed_plugin.as_str(), "plugin_123");
        assert_eq!(parsed_tool, "tool_456_test");
    }

    #[test]
    fn test_borrowed_vs_owned_cow_strings() {
        // Test with borrowed string
        let borrowed_result = parse_namespaced_name("plugin-tool".to_string());
        assert!(borrowed_result.is_ok());

        // Test with owned string
        let owned_result = parse_namespaced_name("plugin-tool".to_string());
        assert!(owned_result.is_ok());

        let (plugin1, tool1) = borrowed_result.unwrap();
        let (plugin2, tool2) = owned_result.unwrap();

        assert_eq!(plugin1.as_str(), plugin2.as_str());
        assert_eq!(tool1, tool2);
    }

    #[test]
    fn test_namespaced_tool_format_invariants() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let tool_name = "test_tool";

        let namespaced = create_namespaced_name(&plugin_name, tool_name);

        // Should contain at least one "-" (the separator)
        let hyphen_count = namespaced.matches("-").count();
        assert!(hyphen_count >= 1, "Should contain at least one '-'");

        // Should start with plugin name
        assert!(
            namespaced.starts_with("test_plugin"),
            "Should start with plugin name"
        );

        // Should end with tool name
        assert!(
            namespaced.ends_with("test_tool"),
            "Should end with tool name"
        );

        // Should be in the format "plugin-tool"
        assert_eq!(namespaced, "test_plugin-test_tool");

        // Test parsing works correctly with the first hyphen as separator
        let (parsed_plugin, parsed_tool) = parse_namespaced_name(namespaced).unwrap();
        assert_eq!(parsed_plugin.as_str(), "test_plugin");
        assert_eq!(parsed_tool, "test_tool");
    }

    // Tests for create_namespaced_uri and parse_namespaced_uri

    #[test]
    fn test_create_namespaced_uri_basic() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com/api/endpoint";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(result, "http://example.com/test_plugin/api/endpoint");
    }

    #[test]
    fn test_create_namespaced_uri_root_path() {
        let plugin_name = PluginName::from_str("my_plugin").unwrap();
        let uri = "http://example.com/";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(result, "http://example.com/my_plugin/");
    }

    #[test]
    fn test_create_namespaced_uri_no_path() {
        let plugin_name = PluginName::from_str("my_plugin").unwrap();
        let uri = "http://example.com";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(result, "http://example.com/my_plugin/");
    }

    #[test]
    fn test_create_namespaced_uri_with_query_string() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com/api/endpoint?key=value&foo=bar";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        // Query string should be preserved
        assert!(result.contains("test_plugin/api/endpoint"));
        assert!(result.contains("key=value"));
        assert!(result.contains("foo=bar"));
    }

    #[test]
    fn test_create_namespaced_uri_with_fragment() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com/api/endpoint#section";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert!(result.contains("test_plugin/api/endpoint"));
        assert!(result.contains("#section"));
    }

    #[test]
    fn test_create_namespaced_uri_with_port() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com:8080/api/endpoint";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(result, "http://example.com:8080/test_plugin/api/endpoint");
    }

    #[test]
    fn test_create_namespaced_uri_https() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "https://secure.example.com/api/endpoint";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(
            result,
            "https://secure.example.com/test_plugin/api/endpoint"
        );
    }

    #[test]
    fn test_create_namespaced_uri_leading_slash_path() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com//api/endpoint";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert!(result.contains("test_plugin"));
    }

    #[test]
    fn test_create_namespaced_uri_deep_path() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com/v1/api/v2/endpoint/deep";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(
            result,
            "http://example.com/test_plugin/v1/api/v2/endpoint/deep"
        );
    }

    #[test]
    fn test_create_namespaced_uri_invalid_url() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "not a valid url";

        let result = create_namespaced_uri(&plugin_name, uri);
        assert!(result.is_err());
    }

    #[test]
    fn test_create_namespaced_uri_with_underscores_in_plugin_name() {
        let plugin_name = PluginName::from_str("my_test_plugin_123").unwrap();
        let uri = "http://example.com/api";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(result, "http://example.com/my_test_plugin_123/api");
    }

    #[test]
    fn test_parse_namespaced_uri_basic() {
        let namespaced_uri = "http://example.com/test_plugin/api/endpoint".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "test_plugin");
        assert_eq!(uri, "http://example.com/api/endpoint");
    }

    #[test]
    fn test_parse_namespaced_uri_root_path() {
        let namespaced_uri = "http://example.com/my_plugin/".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "my_plugin");
        assert_eq!(uri, "http://example.com/");
    }

    #[test]
    fn test_parse_namespaced_uri_with_query_string() {
        let namespaced_uri = "http://example.com/test_plugin/api/endpoint?key=value".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "test_plugin");
        assert!(uri.contains("api/endpoint"));
        assert!(uri.contains("key=value"));
    }

    #[test]
    fn test_parse_namespaced_uri_with_fragment() {
        let namespaced_uri = "http://example.com/test_plugin/api/endpoint#section".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "test_plugin");
        assert!(uri.contains("api/endpoint"));
        assert!(uri.contains("#section"));
    }

    #[test]
    fn test_parse_namespaced_uri_with_port() {
        let namespaced_uri = "http://example.com:8080/test_plugin/api/endpoint".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "test_plugin");
        assert_eq!(uri, "http://example.com:8080/api/endpoint");
    }

    #[test]
    fn test_parse_namespaced_uri_https() {
        let namespaced_uri = "https://secure.example.com/test_plugin/api/endpoint".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "test_plugin");
        assert_eq!(uri, "https://secure.example.com/api/endpoint");
    }

    #[test]
    fn test_parse_namespaced_uri_deep_path() {
        let namespaced_uri = "http://example.com/test_plugin/v1/api/v2/endpoint/deep".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "test_plugin");
        assert_eq!(uri, "http://example.com/v1/api/v2/endpoint/deep");
    }

    #[test]
    fn test_parse_namespaced_uri_invalid_url() {
        let namespaced_uri = "not a valid url".to_string();

        let result = parse_namespaced_uri(namespaced_uri);
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_namespaced_uri_no_path() {
        let namespaced_uri = "http://example.com".to_string();

        let result = parse_namespaced_uri(namespaced_uri);
        // Should fail because there's no path segment for plugin name
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_namespaced_uri_only_plugin() {
        let namespaced_uri = "http://example.com/test_plugin".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "test_plugin");
        assert_eq!(uri, "http://example.com/");
    }

    #[test]
    fn test_round_trip_uri_operations() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let original_uri = "http://example.com/api/endpoint";

        let namespaced = create_namespaced_uri(&plugin_name, original_uri).unwrap();
        let (parsed_plugin, parsed_uri) = parse_namespaced_uri(namespaced).unwrap();

        assert_eq!(parsed_plugin.as_str(), "test_plugin");
        assert_eq!(parsed_uri, original_uri);
    }

    #[test]
    fn test_round_trip_uri_with_query_and_fragment() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let original_uri = "http://example.com/api/endpoint?key=value#section";

        let namespaced = create_namespaced_uri(&plugin_name, original_uri).unwrap();
        let (parsed_plugin, parsed_uri) = parse_namespaced_uri(namespaced).unwrap();

        assert_eq!(parsed_plugin.as_str(), "test_plugin");
        assert_eq!(parsed_uri, original_uri);
    }

    #[test]
    fn test_uri_with_special_characters_in_path() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com/api/resource-123_test";

        let namespaced = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(
            namespaced,
            "http://example.com/test_plugin/api/resource-123_test"
        );

        let (parsed_plugin, parsed_uri) = parse_namespaced_uri(namespaced).unwrap();
        assert_eq!(parsed_plugin.as_str(), "test_plugin");
        assert_eq!(parsed_uri, uri);
    }

    #[test]
    fn test_create_namespaced_uri_with_empty_path() {
        let plugin_name = PluginName::from_str("test_plugin").unwrap();
        let uri = "http://example.com/";

        let result = create_namespaced_uri(&plugin_name, uri).unwrap();
        assert_eq!(result, "http://example.com/test_plugin/");
    }

    #[test]
    fn test_parse_namespaced_uri_with_underscores_in_plugin() {
        let namespaced_uri = "http://example.com/my_test_plugin_123/api/resource".to_string();

        let (plugin_name, uri) = parse_namespaced_uri(namespaced_uri).unwrap();
        assert_eq!(plugin_name.as_str(), "my_test_plugin_123");
        assert_eq!(uri, "http://example.com/api/resource");
    }
}

```

--------------------------------------------------------------------------------
/examples/plugins/v1/fs/src/lib.rs:
--------------------------------------------------------------------------------

```rust
mod pdk;

use std::fs::{self, OpenOptions};
use std::io::{self, Write};
use std::path::Path;
use std::time::SystemTime;

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> {
    info!("call: {:?}", input);
    match input.params.name.as_str() {
        "read_file" => read_file(input),
        "read_multiple_files" => read_multiple_files(input),
        "write_file" => write_file(input),
        "edit_file" => edit_file(input),
        "create_dir" => create_dir(input),
        "list_dir" => list_dir(input),
        "move_file" => move_file(input),
        "search_files" => search_files(input),
        "get_file_info" => get_file_info(input),
        _ => Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some(format!("Unknown operation: {}", input.params.name)),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        }),
    }
}

fn read_file(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let Some(Value::String(path)) = args.get("path") {
        match fs::read_to_string(path) {
            Ok(content) => Ok(CallToolResult {
                is_error: None,
                content: vec![Content {
                    annotations: None,
                    text: Some(content),
                    mime_type: Some("text/plain".to_string()),
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("Failed to read file: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide a path".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn read_multiple_files(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let Some(Value::Array(paths)) = args.get("paths") {
        let mut results = Vec::new();
        for path in paths {
            if let Value::String(path_str) = path {
                match fs::read_to_string(path_str) {
                    Ok(content) => results.push(json!({
                        "path": path_str,
                        "content": content,
                        "error": null
                    })),
                    Err(e) => results.push(json!({
                        "path": path_str,
                        "content": null,
                        "error": e.to_string()
                    })),
                }
            }
        }
        Ok(CallToolResult {
            is_error: None,
            content: vec![Content {
                annotations: None,
                text: Some(serde_json::to_string(&results)?),
                mime_type: Some("application/json".to_string()),
                r#type: ContentType::Text,
                data: None,
            }],
        })
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide an array of paths".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn write_file(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let (Some(Value::String(path)), Some(Value::String(content))) =
        (args.get("path"), args.get("content"))
    {
        match fs::write(path, content) {
            Ok(_) => Ok(CallToolResult {
                is_error: None,
                content: vec![Content {
                    annotations: None,
                    text: Some("File written successfully".into()),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("Failed to write file: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide path and content".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn edit_file(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let (Some(Value::String(path)), Some(Value::String(content))) =
        (args.get("path"), args.get("content"))
    {
        let mut file = OpenOptions::new().write(true).truncate(true).open(path)?;
        file.write_all(content.as_bytes())?;
        Ok(CallToolResult {
            is_error: None,
            content: vec![Content {
                annotations: None,
                text: Some("File edited successfully".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide path and content".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn create_dir(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let Some(Value::String(path)) = args.get("path") {
        match fs::create_dir_all(path) {
            Ok(_) => Ok(CallToolResult {
                is_error: None,
                content: vec![Content {
                    annotations: None,
                    text: Some("Directory created successfully".into()),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("Failed to create directory: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide a path".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn list_dir(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let Some(Value::String(path)) = args.get("path") {
        match fs::read_dir(path) {
            Ok(entries) => {
                let mut items = Vec::new();
                for entry in entries {
                    if let Ok(entry) = entry {
                        let path = entry.path();
                        let metadata = entry.metadata()?;
                        items.push(json!({
                            "name": entry.file_name().to_string_lossy(),
                            "path": path.to_string_lossy(),
                            "is_file": metadata.is_file(),
                            "is_dir": metadata.is_dir(),
                            "size": metadata.len(),
                            "modified": metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs()
                        }));
                    }
                }
                Ok(CallToolResult {
                    is_error: None,
                    content: vec![Content {
                        annotations: None,
                        text: Some(serde_json::to_string(&items)?),
                        mime_type: Some("application/json".to_string()),
                        r#type: ContentType::Text,
                        data: None,
                    }],
                })
            }
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("Failed to list directory: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide a path".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn move_file(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let (Some(Value::String(from)), Some(Value::String(to))) = (args.get("from"), args.get("to"))
    {
        match fs::rename(from, to) {
            Ok(_) => Ok(CallToolResult {
                is_error: None,
                content: vec![Content {
                    annotations: None,
                    text: Some("File moved successfully".into()),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("Failed to move file: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide from and to paths".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn search_files(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let (Some(Value::String(dir)), Some(Value::String(pattern))) =
        (args.get("directory"), args.get("pattern"))
    {
        let mut results = Vec::new();
        fn search_dir(dir: &Path, pattern: &str, results: &mut Vec<String>) -> io::Result<()> {
            for entry in fs::read_dir(dir)? {
                let entry = entry?;
                let path = entry.path();
                if path.is_dir() {
                    search_dir(&path, pattern, results)?;
                } else if path
                    .file_name()
                    .unwrap_or_default()
                    .to_string_lossy()
                    .contains(pattern)
                {
                    results.push(path.to_string_lossy().into_owned());
                }
            }
            Ok(())
        }
        match search_dir(Path::new(dir), pattern, &mut results) {
            Ok(_) => Ok(CallToolResult {
                is_error: None,
                content: vec![Content {
                    annotations: None,
                    text: Some(serde_json::to_string(&results)?),
                    mime_type: Some("application/json".to_string()),
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("Failed to search files: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide directory and pattern".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

fn get_file_info(input: CallToolRequest) -> Result<CallToolResult, Error> {
    let args = input.params.arguments.clone().unwrap_or_default();
    if let Some(Value::String(path)) = args.get("path") {
        match fs::metadata(path) {
            Ok(metadata) => {
                let info = json!({
                    "size": metadata.len(),
                    "is_file": metadata.is_file(),
                    "is_dir": metadata.is_dir(),
                    "modified": metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(),
                    "created": metadata.created()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(),
                    "accessed": metadata.accessed()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(),
                });
                Ok(CallToolResult {
                    is_error: None,
                    content: vec![Content {
                        annotations: None,
                        text: Some(serde_json::to_string(&info)?),
                        mime_type: Some("application/json".to_string()),
                        r#type: ContentType::Text,
                        data: None,
                    }],
                })
            }
            Err(e) => Ok(CallToolResult {
                is_error: Some(true),
                content: vec![Content {
                    annotations: None,
                    text: Some(format!("Failed to get file info: {}", e)),
                    mime_type: None,
                    r#type: ContentType::Text,
                    data: None,
                }],
            }),
        }
    } else {
        Ok(CallToolResult {
            is_error: Some(true),
            content: vec![Content {
                annotations: None,
                text: Some("Please provide a path".into()),
                mime_type: None,
                r#type: ContentType::Text,
                data: None,
            }],
        })
    }
}

pub(crate) fn describe() -> Result<ListToolsResult, Error> {
    Ok(ListToolsResult {
        tools: vec![
            ToolDescription {
                name: "read_file".into(),
                description: "Read the contents of a file".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Path to the file to read",
                        },
                    },
                    "required": ["path"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "read_multiple_files".into(),
                description: "Read contents of multiple files".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "paths": {
                            "type": "array",
                            "items": {
                                "type": "string"
                            },
                            "description": "Array of file paths to read",
                        },
                    },
                    "required": ["paths"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "write_file".into(),
                description: "Write content to a file".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Path where to write the file",
                        },
                        "content": {
                            "type": "string",
                            "description": "Content to write to the file",
                        },
                    },
                    "required": ["path", "content"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "edit_file".into(),
                description: "Edit an existing file's content".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Path to the file to edit",
                        },
                        "content": {
                            "type": "string",
                            "description": "New content for the file",
                        },
                    },
                    "required": ["path", "content"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "create_dir".into(),
                description: "Create a new directory".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Path where to create the directory",
                        },
                    },
                    "required": ["path"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "list_dir".into(),
                description: "List contents of a directory".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Path to the directory to list",
                        },
                    },
                    "required": ["path"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "move_file".into(),
                description: "Move a file from one location to another".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "from": {
                            "type": "string",
                            "description": "Source path of the file",
                        },
                        "to": {
                            "type": "string",
                            "description": "Destination path for the file",
                        },
                    },
                    "required": ["from", "to"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "search_files".into(),
                description: "Search for files matching a pattern in a directory".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "directory": {
                            "type": "string",
                            "description": "Directory to search in",
                        },
                        "pattern": {
                            "type": "string",
                            "description": "Pattern to match against filenames",
                        },
                    },
                    "required": ["directory", "pattern"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
            ToolDescription {
                name: "get_file_info".into(),
                description: "Get information about a file or directory".into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "Path to get information about",
                        },
                    },
                    "required": ["path"],
                })
                .as_object()
                .unwrap()
                .clone(),
            },
        ],
    })
}

```

--------------------------------------------------------------------------------
/templates/plugins/go/types.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"encoding/json"
	"fmt"
	"time"
)

// Annotations represents metadata annotations for resources and content
type Annotations struct {
	Audience     []Role     `json:"audience,omitempty"`
	LastModified *time.Time `json:"lastModified,omitempty"`
	Priority     float32    `json:"priority,omitempty"`
}

// AudioContent represents audio content in a message
type AudioContent struct {
	Meta        Meta         `json:"_meta,omitempty"`
	Annotations *Annotations `json:"annotations,omitempty"`
	Data        string       `json:"data"`
	MimeType    string       `json:"mimeType"`
}

func (a AudioContent) MarshalJSON() ([]byte, error) {
	type alias AudioContent
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "audio",
		alias: (alias)(a),
	})
}

func (a *AudioContent) UnmarshalJSON(data []byte) error {
	type alias AudioContent
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	// Optional: validate `type`
	if aux.Type != "audio" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"audio\"", aux.Type)
	}

	*a = AudioContent(aux.alias)
	return nil
}

// BlobResourceContents represents binary resource contents
type BlobResourceContents struct {
	Meta     Meta    `json:"_meta,omitempty"`
	Blob     string  `json:"blob"`
	MimeType *string `json:"mimeType,omitempty"`
	URI      string  `json:"uri"`
}

// BooleanSchema represents a boolean input schema
type BooleanSchema struct {
	Default     *bool   `json:"default,omitempty"`
	Description *string `json:"description,omitempty"`
	Title       *string `json:"title,omitempty"`
}

func (b BooleanSchema) MarshalJSON() ([]byte, error) {
	type alias BooleanSchema
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "boolean",
		alias: (alias)(b),
	})
}

func (b *BooleanSchema) UnmarshalJSON(data []byte) error {
	type alias BooleanSchema
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	// Optional: validate `type`
	if aux.Type != "boolean" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"boolean\"", aux.Type)
	}

	*b = BooleanSchema(aux.alias)
	return nil
}

// CallToolRequest represents a request to call a tool
type CallToolRequest struct {
	Context PluginRequestContext `json:"context"`
	Request CallToolRequestParam `json:"request"`
}

// CallToolRequestParam represents parameters for calling a tool
type CallToolRequestParam struct {
	Arguments map[string]any `json:"arguments,omitempty"`
	Name      string         `json:"name"`
}

// CallToolResult represents the result of calling a tool
type CallToolResult struct {
	Meta              Meta           `json:"_meta,omitempty"`
	Content           []ContentBlock `json:"content"`
	IsError           *bool          `json:"isError,omitempty"`
	StructuredContent map[string]any `json:"structuredContent,omitempty"`
}

// CompleteRequest represents a request for completion suggestions
type CompleteRequest struct {
	Context PluginRequestContext `json:"context"`
	Request CompleteRequestParam `json:"request"`
}

// CompleteRequestParam represents parameters for completion
type CompleteRequestParam struct {
	Argument CompleteRequestParamArgument `json:"argument"`
	Context  *CompleteRequestParamContext `json:"context,omitempty"`
	Ref      Reference                    `json:"ref"`
}

// CompleteRequestParamArgument represents an argument for completion
type CompleteRequestParamArgument struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

// CompleteRequestParamContext represents context for completion
type CompleteRequestParamContext struct {
	Arguments map[string]string `json:"arguments,omitempty"`
}

// CompleteResult represents completion suggestions
type CompleteResult struct {
	Completion CompleteResultCompletion `json:"completion"`
}

// CompleteResultCompletion represents completion values
type CompleteResultCompletion struct {
	HasMore *bool    `json:"hasMore,omitempty"`
	Total   *int64   `json:"total,omitempty"`
	Values  []string `json:"values"`
}

type ContentBlock struct {
	Audio            *AudioContent
	EmbeddedResource *EmbeddedResource
	Image            *ImageContent
	ResourceLink     *ResourceLinkContent
	Text             *TextContent
}

func (c ContentBlock) MarshalJSON() ([]byte, error) {
	switch {
	case c.Audio != nil:
		return json.Marshal(c.Audio)
	case c.EmbeddedResource != nil:
		return json.Marshal(c.EmbeddedResource)
	case c.Image != nil:
		return json.Marshal(c.Image)
	case c.ResourceLink != nil:
		return json.Marshal(c.ResourceLink)
	case c.Text != nil:
		return json.Marshal(c.Text)
	default:
		return nil, fmt.Errorf("empty ContentItem")
	}
}

func (c *ContentBlock) UnmarshalJSON(data []byte) error {
	var head struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(data, &head); err != nil {
		return err
	}

	switch head.Type {
	case "audio":
		var a AudioContent
		if err := json.Unmarshal(data, &a); err != nil {
			return err
		}
		c.Audio = &a
	case "resource":
		var r EmbeddedResource
		if err := json.Unmarshal(data, &r); err != nil {
			return err
		}
		c.EmbeddedResource = &r
	case "image":
		var i ImageContent
		if err := json.Unmarshal(data, &i); err != nil {
			return err
		}
		c.Image = &i
	case "resource_link":
		var rl ResourceLinkContent
		if err := json.Unmarshal(data, &rl); err != nil {
			return err
		}
		c.ResourceLink = &rl
	case "text":
		var t TextContent
		if err := json.Unmarshal(data, &t); err != nil {
			return err
		}
		c.Text = &t
	default:
		return fmt.Errorf("unknown content type %q", head.Type)
	}

	return nil
}

// CreateMessageRequestParam represents a request to create a message
type CreateMessageRequestParam struct {
	IncludeContext   *CreateMessageRequestParamIncludeContext `json:"includeContext,omitempty"`
	MaxTokens        int64                                    `json:"maxTokens"`
	Messages         []SamplingMessage                        `json:"messages"`
	ModelPreferences *ModelPreferences                        `json:"modelPreferences,omitempty"`
	StopSequences    []string                                 `json:"stopSequences,omitempty"`
	SystemPrompt     *string                                  `json:"systemPrompt,omitempty"`
	Temperature      *float64                                 `json:"temperature,omitempty"`
}

// CreateMessageRequestParamIncludeContext represents context inclusion options
type CreateMessageRequestParamIncludeContext string

const (
	AllServers CreateMessageRequestParamIncludeContext = "allServers"
	None       CreateMessageRequestParamIncludeContext = "none"
	ThisServer CreateMessageRequestParamIncludeContext = "thisServer"
)

func (t *CreateMessageRequestParamIncludeContext) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	ct := CreateMessageRequestParamIncludeContext(s)
	if !ct.Valid() {
		return fmt.Errorf("invalid CreateMessageRequestParamIncludeContext %q", s)
	}

	*t = ct
	return nil
}

func (t CreateMessageRequestParamIncludeContext) Valid() bool {
	switch t {
	case AllServers, None, ThisServer:
		return true
	default:
		return false
	}
}

// CreateMessageResult represents the result of creating a message
type CreateMessageResult struct {
	Content    CreateMessageResultContent `json:"content"`
	Model      string                     `json:"model"`
	Role       Role                       `json:"role"`
	StopReason *string                    `json:"stopReason,omitempty"`
}

type CreateMessageResultContent SamplingMessage

// ElicitRequestParamWithTimeout represents a request for user elicitation
type ElicitRequestParamWithTimeout struct {
	Message         string `json:"message"`
	RequestedSchema Schema `json:"requestedSchema"`
	Timeout         *int64 `json:"timeout,omitempty"`
}

// ElicitResult represents the result of an elicitation
type ElicitResult struct {
	Action  ElicitResultAction                  `json:"action"`
	Content map[string]ElicitResultContentValue `json:"content,omitempty"`
}

// ElicitResultAction represents the action taken in elicitation
type ElicitResultAction string

const (
	Accept  ElicitResultAction = "accept"
	Cancel  ElicitResultAction = "cancel"
	Decline ElicitResultAction = "decline"
)

func (e *ElicitResultAction) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	ea := ElicitResultAction(s)
	if !ea.Valid() {
		return fmt.Errorf("invalid ElicitResultAction %q", s)
	}

	*e = ea
	return nil
}

func (e ElicitResultAction) Valid() bool {
	switch e {
	case Accept, Cancel, Decline:
		return true
	default:
		return false
	}
}

type ElicitResultContentValue struct {
	String  *string
	Number  *json.Number
	Boolean *bool
}

func (v ElicitResultContentValue) MarshalJSON() ([]byte, error) {
	switch {
	case v.String != nil:
		return json.Marshal(v.String)
	case v.Number != nil:
		return json.Marshal(v.Number)
	case v.Boolean != nil:
		return json.Marshal(v.Boolean)
	default:
		return nil, fmt.Errorf("ElicitResultContentValue has no value set")
	}
}

func (v *ElicitResultContentValue) UnmarshalJSON(data []byte) error {
	// Clear existing values
	*v = ElicitResultContentValue{}

	// Try string first
	var s string
	if err := json.Unmarshal(data, &s); err == nil {
		v.String = &s
		return nil
	}

	// Then bool
	var b bool
	if err := json.Unmarshal(data, &b); err == nil {
		v.Boolean = &b
		return nil
	}

	// Then number
	var n json.Number
	if err := json.Unmarshal(data, &n); err == nil {
		v.Number = &n
		return nil
	}

	// If all fail, it's not a valid primitive for this type
	return fmt.Errorf("ElicitResultContentValue: unsupported JSON value: %s", string(data))
}

// EmbeddedResource represents an embedded resource
type EmbeddedResource struct {
	Meta        Meta             `json:"_meta,omitempty"`
	Annotations *Annotations     `json:"annotations,omitempty"`
	Resource    ResourceContents `json:"resource"`
}

func (e EmbeddedResource) MarshalJSON() ([]byte, error) {
	type alias EmbeddedResource

	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "resource",
		alias: (alias)(e),
	})
}

func (e *EmbeddedResource) UnmarshalJSON(data []byte) error {
	type alias EmbeddedResource
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.Type != "resource" && aux.Type != "" {
		return fmt.Errorf("invalid type %q, expected \"resource\"", aux.Type)
	}

	*e = EmbeddedResource(aux.alias)
	return nil
}

// EnumSchema represents an enum input schema
type EnumSchema struct {
	Description *string  `json:"description,omitempty"`
	Enum        []string `json:"enum"`
	EnumNames   []string `json:"enumNames,omitempty"`
	Title       *string  `json:"title,omitempty"`
}

func (e EnumSchema) MarshallJSON() ([]byte, error) {
	type alias EnumSchema

	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "resource",
		alias: (alias)(e),
	})
}

func (e *EnumSchema) UnmarshalJSON(data []byte) error {
	type alias EnumSchema
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.Type != "string" && aux.Type != "" {
		return fmt.Errorf("invalid type %q, expected \"string\"", aux.Type)
	}

	*e = EnumSchema(aux.alias)
	return nil
}

// GetPromptRequest represents a request to get a prompt
type GetPromptRequest struct {
	Context PluginRequestContext  `json:"context"`
	Request GetPromptRequestParam `json:"request"`
}

// GetPromptRequestParam represents parameters for getting a prompt
type GetPromptRequestParam struct {
	Arguments map[string]string `json:"arguments,omitempty"`
	Name      string            `json:"name"`
}

// GetPromptResult represents the result of getting a prompt
type GetPromptResult struct {
	Description *string         `json:"description,omitempty"`
	Messages    []PromptMessage `json:"messages"`
}

// ImageContent represents image content
type ImageContent struct {
	Meta        Meta         `json:"_meta,omitempty"`
	Annotations *Annotations `json:"annotations,omitempty"`
	Data        string       `json:"data"`
	MimeType    string       `json:"mimeType"`
}

func (i ImageContent) MarshallJSON() ([]byte, error) {
	type alias ImageContent

	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "image",
		alias: (alias)(i),
	})
}

func (i *ImageContent) UnmarshalJSON(data []byte) error {
	type alias ImageContent
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.Type != "image" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"image\"", aux.Type)
	}

	*i = ImageContent(aux.alias)
	return nil
}

// ListPromptsRequest represents a request to list prompts
type ListPromptsRequest struct {
	Context PluginRequestContext `json:"context"`
}

// ListPromptsResult represents the result of listing prompts
type ListPromptsResult struct {
	Prompts []Prompt `json:"prompts"`
}

// ListResourcesRequest represents a request to list resources
type ListResourcesRequest struct {
	Context PluginRequestContext `json:"context"`
}

// ListResourcesResult represents the result of listing resources
type ListResourcesResult struct {
	Resources []Resource `json:"resources"`
}

// ListResourceTemplatesRequest represents a request to list resource templates
type ListResourceTemplatesRequest struct {
	Context PluginRequestContext `json:"context"`
}

// ListResourceTemplatesResult represents the result of listing resource templates
type ListResourceTemplatesResult struct {
	ResourceTemplates []ResourceTemplate `json:"resourceTemplates"`
}

// ListRootsResult represents the result of listing roots
type ListRootsResult struct {
	Roots []Root `json:"roots"`
}

// ListToolsRequest represents a request to list tools
type ListToolsRequest struct {
	Context PluginRequestContext `json:"context"`
}

// ListToolsResult represents the result of listing tools
type ListToolsResult struct {
	Tools []Tool `json:"tools"`
}

// LoggingLevel represents the severity level of a log message
type LoggingLevel string

const (
	Debug     LoggingLevel = "debug"
	Info      LoggingLevel = "info"
	Notice    LoggingLevel = "notice"
	Warning   LoggingLevel = "warning"
	Error     LoggingLevel = "error"
	Critical  LoggingLevel = "critical"
	Alert     LoggingLevel = "alert"
	Emergency LoggingLevel = "emergency"
)

func (l *LoggingLevel) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	ll := LoggingLevel(s)
	if !ll.Validate() {
		return fmt.Errorf("invalid LoggingLevel %q", s)
	}

	*l = ll
	return nil
}

func (l LoggingLevel) Validate() bool {
	switch l {
	case Debug, Info, Notice, Warning, Error, Critical, Alert, Emergency:
		return true
	default:
		return false
	}
}

// LoggingMessageNotificationParam represents a logging message notification
type LoggingMessageNotificationParam struct {
	Data   any          `json:"data"`
	Level  LoggingLevel `json:"level"`
	Logger *string      `json:"logger,omitempty"`
}

// Meta represents metadata as a generic JSON object
type Meta map[string]any

// ModelHint represents a hint for model selection
type ModelHint struct {
	Name string `json:"name"`
}

// ModelPreferences represents preferences for model selection
type ModelPreferences struct {
	CostPriority         float32     `json:"costPriority,omitempty"`
	Hints                []ModelHint `json:"hints,omitempty"`
	IntelligencePriority float32     `json:"intelligencePriority,omitempty"`
	SpeedPriority        float32     `json:"speedPriority,omitempty"`
}

// NumberSchema represents a number input schema
type NumberSchema struct {
	Description *string    `json:"description,omitempty"`
	Maximum     *float64   `json:"maximum,omitempty"`
	Minimum     *float64   `json:"minimum,omitempty"`
	Title       *string    `json:"title,omitempty"`
	Type        NumberType `json:"type"` // "number" or "integer"
}

// NumberType represents the type of a number schema
type NumberType string

const (
	Number  NumberType = "number"
	Integer NumberType = "integer"
)

func (n *NumberType) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	nt := NumberType(s)
	if !nt.Valid() {
		return fmt.Errorf("invalid NumberType %q", s)
	}

	*n = nt
	return nil
}

func (n NumberType) Valid() bool {
	switch n {
	case Number, Integer:
		return true
	default:
		return false
	}
}

// PluginNotificationContext represents the context for a plugin notification
type PluginNotificationContext struct {
	Meta Meta `json:"meta"`
}

// PluginRequestContext represents the context for a plugin request
type PluginRequestContext struct {
	Meta Meta            `json:"_meta"`
	ID   PluginRequestId `json:"id"`
}

type PluginRequestId struct {
	String *string
	Number *int64
}

func (p PluginRequestId) MarshalJSON() ([]byte, error) {
	switch {
	case p.String != nil:
		return json.Marshal(p.String)
	case p.Number != nil:
		return json.Marshal(p.Number)
	default:
		return nil, fmt.Errorf("empty PluginRequestId")
	}
}

func (p *PluginRequestId) UnmarshalJSON(data []byte) error {
	*p = PluginRequestId{}

	// Try string first
	var s string
	if err := json.Unmarshal(data, &s); err == nil {
		p.String = &s
		return nil
	}

	// Then number
	var n int64
	if err := json.Unmarshal(data, &n); err == nil {
		p.Number = &n
		return nil
	}

	// If all fail, it's not a valid primitive for this type
	return fmt.Errorf("PluginRequestId: unsupported JSON value: %s", string(data))
}

// PrimitiveSchemaDefinition is a union type for schema definitions
type PrimitiveSchemaDefinition struct {
	Boolean *BooleanSchema
	Enum    *EnumSchema
	Number  *NumberSchema
	String  *StringSchema
}

func (p PrimitiveSchemaDefinition) MarshalJSON() ([]byte, error) {
	switch {
	case p.Boolean != nil:
		return json.Marshal(p.Boolean)
	case p.Enum != nil:
		return json.Marshal(p.Enum)
	case p.Number != nil:
		return json.Marshal(p.Number)
	case p.String != nil:
		return json.Marshal(p.String)
	default:
		return nil, fmt.Errorf("empty PrimitiveSchemaDefinition")
	}
}

func (p *PrimitiveSchemaDefinition) UnmarshalJSON(data []byte) error {
	var head struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(data, &head); err != nil {
		return err
	}

	switch head.Type {
	case "boolean":
		var b BooleanSchema
		if err := json.Unmarshal(data, &b); err != nil {
			return err
		}
		p.Boolean = &b
	case "string":
		var e EnumSchema
		if err := json.Unmarshal(data, &e); err != nil {
			var s StringSchema
			if err := json.Unmarshal(data, &s); err != nil {
				return err
			}
			p.String = &s
		} else {
			p.Enum = &e
		}
	case "number", "integer":
		var n NumberSchema
		if err := json.Unmarshal(data, &n); err != nil {
			return err
		}
		p.Number = &n
	}

	return nil
}

// ProgressNotificationParam represents a progress notification
type ProgressNotificationParam struct {
	Message       *string  `json:"message,omitempty"`
	Progress      float64  `json:"progress"`
	ProgressToken string   `json:"progressToken"`
	Total         *float64 `json:"total,omitempty"`
}

// Prompt represents a prompt
type Prompt struct {
	Arguments   []PromptArgument `json:"arguments,omitempty"`
	Description *string          `json:"description,omitempty"`
	Name        string           `json:"name"`
	Title       *string          `json:"title,omitempty"`
}

// PromptArgument represents an argument for a prompt
type PromptArgument struct {
	Description *string `json:"description,omitempty"`
	Name        string  `json:"name"`
	Required    *bool   `json:"required,omitempty"`
	Title       *string `json:"title,omitempty"`
}

// PromptMessage represents a message in a prompt
type PromptMessage struct {
	Content ContentBlock `json:"content"`
	Role    Role         `json:"role"`
}

// PromptReference represents a reference to a prompt
type PromptReference struct {
	Name  string  `json:"name"`
	Title *string `json:"title,omitempty"`
}

func (p PromptReference) MarshalJSON() ([]byte, error) {
	type alias PromptReference
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "prompt",
		alias: (alias)(p),
	})
}

func (p *PromptReference) UnmarshalJSON(data []byte) error {
	type alias PromptReference
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.Type != "prompt" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"prompt\"", aux.Type)
	}

	*p = PromptReference(aux.alias)
	return nil
}

// ReadResourceRequest represents a request to read a resource
type ReadResourceRequest struct {
	Context PluginRequestContext     `json:"context"`
	Request ReadResourceRequestParam `json:"request"`
}

// ReadResourceRequestParam represents parameters for reading a resource
type ReadResourceRequestParam struct {
	URI string `json:"uri"`
}

// ReadResourceResult represents the result of reading a resource
type ReadResourceResult struct {
	Contents []ResourceContents `json:"contents"`
}

type Reference struct {
	Prompt           *PromptReference
	ResourceTemplate *ResourceTemplateReference
}

func (r Reference) MarshalJSON() ([]byte, error) {
	switch {
	case r.Prompt != nil:
		return json.Marshal(r.Prompt)
	case r.ResourceTemplate != nil:
		return json.Marshal(r.ResourceTemplate)
	default:
		return nil, fmt.Errorf("empty Reference")
	}
}

func (r *Reference) UnmarshalJSON(data []byte) error {
	var head struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(data, &head); err != nil {
		return err
	}

	switch head.Type {
	case "prompt":
		var p PromptReference
		if err := json.Unmarshal(data, &p); err != nil {
			return err
		}
		r.Prompt = &p
	case "resource":
		var rt ResourceTemplateReference
		if err := json.Unmarshal(data, &rt); err != nil {
			return err
		}
		r.ResourceTemplate = &rt
	default:
		return fmt.Errorf("unknown reference type %q", head.Type)
	}

	return nil
}

// Resource represents a resource
type Resource struct {
	Annotations *Annotations `json:"annotations,omitempty"`
	Description *string      `json:"description,omitempty"`
	MimeType    *string      `json:"mimeType,omitempty"`
	Name        string       `json:"name"`
	Size        *int64       `json:"size,omitempty"`
	Title       *string      `json:"title,omitempty"`
	URI         string       `json:"uri"`
}

type ResourceContents struct {
	Blob *BlobResourceContents
	Text *TextResourceContents
}

func (R ResourceContents) MarshalJSON() ([]byte, error) {
	switch {
	case R.Blob != nil:
		return json.Marshal(R.Blob)
	case R.Text != nil:
		return json.Marshal(R.Text)
	default:
		return nil, fmt.Errorf("empty ResourceContents")
	}
}

func (r *ResourceContents) UnmarshalJSON(data []byte) error {
	// Clear existing values
	*r = ResourceContents{}

	// Try blob first
	var b BlobResourceContents
	if err := json.Unmarshal(data, &b); err == nil {
		r.Blob = &b
		return nil
	}

	// Then text
	var t TextResourceContents
	if err := json.Unmarshal(data, &t); err == nil {
		r.Text = &t
		return nil
	}

	// If all fail, it's not a valid ResourceContents
	return fmt.Errorf("ResourceContents: unsupported JSON value: %s", string(data))
}

// ResourceLinkContent represents a link to a resource
type ResourceLinkContent struct {
	Meta        Meta         `json:"_meta,omitempty"`
	Annotations *Annotations `json:"annotations,omitempty"`
	Description *string      `json:"description,omitempty"`
	MimeType    *string      `json:"mimeType,omitempty"`
	Name        string       `json:"name"`
	Size        *int64       `json:"size,omitempty"`
	Title       *string      `json:"title,omitempty"`
	URI         string       `json:"uri"`
}

func (r ResourceLinkContent) MarshallJSON() ([]byte, error) {
	type alias ResourceLinkContent
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "resource_link",
		alias: (alias)(r),
	})
}

func (r *ResourceLinkContent) UnmarshalJSON(data []byte) error {
	type alias ResourceLinkContent
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.Type != "resource_link" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"resource_link\"", aux.Type)
	}

	*r = ResourceLinkContent(aux.alias)
	return nil
}

// ResourceTemplate represents a resource template
type ResourceTemplate struct {
	Annotations *Annotations `json:"annotations,omitempty"`
	Description *string      `json:"description,omitempty"`
	MimeType    *string      `json:"mimeType,omitempty"`
	Name        string       `json:"name"`
	Title       *string      `json:"title,omitempty"`
	URITemplate string       `json:"uriTemplate"`
}

// ResourceTemplateReference represents a reference to a resource template
type ResourceTemplateReference struct {
	URI string `json:"uri"`
}

func (r ResourceTemplateReference) MarshallJSON() ([]byte, error) {
	type alias ResourceTemplateReference
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "resource",
		alias: (alias)(r),
	})
}

func (r *ResourceTemplateReference) UnmarshalJSON(data []byte) error {
	type alias ResourceTemplateReference
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.Type != "resource" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"resource\"", aux.Type)
	}

	*r = ResourceTemplateReference(aux.alias)
	return nil
}

// ResourceUpdatedNotificationParam represents a resource update notification
type ResourceUpdatedNotificationParam struct {
	URI string `json:"uri"`
}

// Role represents the role of a message sender
type Role string

const (
	Assistant Role = "assistant"
	User      Role = "user"
)

func (r *Role) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	rr := Role(s)
	if !rr.Valid() {
		return fmt.Errorf("invalid Role %q", s)
	}

	*r = rr
	return nil
}

func (r Role) Valid() bool {
	switch r {
	case Assistant, User:
		return true
	default:
		return false
	}
}

// Root represents a root directory or resource
type Root struct {
	Name *string `json:"name,omitempty"`
	URI  string  `json:"uri"`
}

type SamplingMessage struct {
	Audio *AudioContent
	Image *ImageContent
	Text  *TextContent
}

func (s SamplingMessage) MarshalJSON() ([]byte, error) {
	switch {
	case s.Audio != nil:
		return json.Marshal(s.Audio)
	case s.Image != nil:
		return json.Marshal(s.Image)
	case s.Text != nil:
		return json.Marshal(s.Text)
	default:
		return nil, fmt.Errorf("empty SamplingMessage")
	}
}

func (s *SamplingMessage) UnmarshalJSON(data []byte) error {
	var head struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(data, &head); err != nil {
		return err
	}

	switch head.Type {
	case "audio":
		var a AudioContent
		if err := json.Unmarshal(data, &a); err != nil {
			return err
		}
		s.Audio = &a
	case "image":
		var i ImageContent
		if err := json.Unmarshal(data, &i); err != nil {
			return err
		}
		s.Image = &i
	case "text":
		var t TextContent
		if err := json.Unmarshal(data, &t); err != nil {
			return err
		}
		s.Text = &t
	default:
		return fmt.Errorf("unknown content type %q", head.Type)
	}

	return nil
}

// Schema represents a JSON schema
type Schema struct {
	Properties map[string]PrimitiveSchemaDefinition `json:"properties,omitempty"`
	Required   []string                             `json:"required,omitempty"`
}

func (s Schema) MarshallJSON() ([]byte, error) {
	type alias Schema
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "object",
		alias: (alias)(s),
	})
}

func (s *Schema) UnmarshalJSON(data []byte) error {
	type alias Schema
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	// Optional: validate `type`
	if aux.Type != "object" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"object\"", aux.Type)
	}

	*s = Schema(aux.alias)
	return nil
}

// StringSchema represents a string input schema
type StringSchema struct {
	Description *string             `json:"description,omitempty"`
	Format      *StringSchemaFormat `json:"format,omitempty"`
	MaxLength   *int64              `json:"maxLength,omitempty"`
	MinLength   *int64              `json:"minLength,omitempty"`
	Title       *string             `json:"title,omitempty"`
}

func (s StringSchema) MarshallJSON() ([]byte, error) {
	type alias StringSchema
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "string",
		alias: (alias)(s),
	})
}

func (s *StringSchema) UnmarshalJSON(data []byte) error {
	type alias StringSchema
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	// Optional: validate `type`
	if aux.Type != "string" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"string\"", aux.Type)
	}

	*s = StringSchema(aux.alias)
	return nil
}

// StringSchemaFormat represents the format of a string schema
type StringSchemaFormat string

const (
	Email    StringSchemaFormat = "email"
	URI      StringSchemaFormat = "uri"
	Date     StringSchemaFormat = "date"
	DateTime StringSchemaFormat = "date_time"
)

func (s *StringSchemaFormat) UnmarshalJSON(data []byte) error {
	var str string
	if err := json.Unmarshal(data, &str); err != nil {
		return err
	}

	sf := StringSchemaFormat(str)
	if !sf.Valid() {
		return fmt.Errorf("invalid StringSchemaFormat %q", str)
	}

	*s = sf
	return nil
}

func (s StringSchemaFormat) Valid() bool {
	switch s {
	case Email, URI, Date, DateTime:
		return true
	default:
		return false
	}
}

// TextContent represents text content
type TextContent struct {
	Meta        Meta         `json:"_meta,omitempty"`
	Annotations *Annotations `json:"annotations,omitempty"`
	Text        string       `json:"text"`
}

func (t TextContent) MarshallJSON() ([]byte, error) {
	type alias TextContent
	return json.Marshal(&struct {
		Type string `json:"type"`
		alias
	}{
		Type:  "text",
		alias: (alias)(t),
	})
}

func (t *TextContent) UnmarshalJSON(data []byte) error {
	type alias TextContent
	aux := struct {
		Type string `json:"type"`
		alias
	}{}

	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}

	if aux.Type != "text" && aux.Type != "" { // allow empty if missing
		return fmt.Errorf("invalid type %q, expected \"text\"", aux.Type)
	}

	*t = TextContent(aux.alias)
	return nil
}

// TextResourceContents represents text resource contents
type TextResourceContents struct {
	Meta     Meta    `json:"_meta,omitempty"`
	MimeType *string `json:"mimeType,omitempty"`
	Text     string  `json:"text"`
	URI      string  `json:"uri"`
}

// Tool represents a tool
type Tool struct {
	Annotations  *Annotations `json:"annotations,omitempty"`
	Description  *string      `json:"description,omitempty"`
	InputSchema  ToolSchema   `json:"inputSchema"`
	Name         string       `json:"name"`
	OutputSchema *ToolSchema  `json:"outputSchema,omitempty"`
	Title        *string      `json:"title,omitempty"`
}

// ToolSchema represents the schema for tool input or output
type ToolSchema struct {
	Properties map[string]any `json:"properties,omitempty"`
	Required   []string       `json:"required,omitempty"`
	Type       string         `json:"type"` // "object"
}

```

--------------------------------------------------------------------------------
/examples/plugins/v2/rstime/src/pdk/types.rs:
--------------------------------------------------------------------------------

```rust
#![allow(unused)]
use base64::engine::general_purpose::STANDARD;
use base64_serde::base64_serde_type;
use extism_pdk::{FromBytes, Json, ToBytes};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Number, Value};
use std::collections::HashMap;

base64_serde_type!(Base64Standard, STANDARD);

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct Annotations {
    /// Intended audience for the resource
    #[serde(rename = "audience")]
    pub audience: Vec<Role>,

    /// Last modified timestamp for the resource
    #[serde(rename = "lastModified")]
    pub last_modified: chrono::DateTime<chrono::Utc>,

    /// Priority level indicating the importance of the resource
    #[serde(rename = "priority")]
    pub priority: f32,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct AudioContent {
    /// Optional additional metadata about the content block
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// Optional content annotations
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// Base64-encoded audio data
    #[serde(rename = "data")]
    pub data: String,

    /// MIME type of the audio (e.g. 'audio/mpeg')
    #[serde(rename = "mimeType")]
    pub mime_type: String,

    #[serde(rename = "type")]
    pub r#type: AudioType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum AudioType {
    #[default]
    #[serde(rename = "audio")]
    Audio,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct BlobResourceContents {
    /// Optional additional metadata about the blob resource
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// Base64-encoded binary data of the resource
    #[serde(rename = "blob")]
    pub blob: String,

    /// MIME type of the binary content (e.g. 'application/pdf')
    #[serde(rename = "mimeType")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub mime_type: Option<String>,

    /// URI of the resource
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct BooleanSchema {
    /// Optional default value
    #[serde(rename = "default")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub default: Option<bool>,

    /// Description of the boolean input
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// Optional human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    #[serde(rename = "type")]
    pub r#type: BooleanType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum BooleanType {
    #[default]
    #[serde(rename = "boolean")]
    Boolean,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CallToolRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,

    #[serde(rename = "request")]
    pub request: CallToolRequestParam,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CallToolRequestParam {
    /// Arguments to pass to the tool
    #[serde(rename = "arguments")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub arguments: Option<Map<String, Value>>,

    /// The name of the tool to call
    #[serde(rename = "name")]
    pub name: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CallToolResult {
    /// Optional additional metadata about the tool call result
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// Array of TextContent, ImageContent, AudioContent, EmbeddedResource, or ResourceLinks representing the result
    #[serde(rename = "content")]
    pub content: Vec<ContentBlock>,

    /// Whether the tool call ended in an error. If not set, defaults to false.
    #[serde(rename = "isError")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub is_error: Option<bool>,

    /// Optional structured JSON result from the tool
    #[serde(rename = "structuredContent")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub structured_content: Option<Map<String, Value>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CompleteRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,

    #[serde(rename = "request")]
    pub request: CompleteRequestParam,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CompleteRequestParam {
    #[serde(rename = "argument")]
    pub argument: CompleteRequestParamArgument,

    /// Optional completion context with previously-resolved arguments
    #[serde(rename = "context")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<CompleteRequestParamContext>,

    /// Reference to either a PromptReference or ResourceTemplateReference
    #[serde(rename = "ref")]
    pub r#ref: Reference,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CompleteRequestParamArgument {
    /// Name of the argument
    #[serde(rename = "name")]
    pub name: String,

    /// Current value to complete
    #[serde(rename = "value")]
    pub value: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CompleteRequestParamContext {
    /// Previously-resolved argument values
    #[serde(rename = "arguments")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub arguments: Option<HashMap<String, String>>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CompleteResult {
    #[serde(rename = "completion")]
    pub completion: CompleteResultCompletion,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CompleteResultCompletion {
    /// Whether there are more completions available
    #[serde(rename = "hasMore")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub has_more: Option<bool>,

    /// Total number of available completions
    #[serde(rename = "total")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub total: Option<i64>,

    /// Array of completion values (max 100 items)
    #[serde(rename = "values")]
    pub values: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
#[serde(untagged)]
pub enum ContentBlock {
    Audio(AudioContent),
    EmbeddedResource(EmbeddedResource),
    Image(ImageContent),
    ResourceLink(ResourceLink),
    Text(TextContent),
    Empty(Empty),
}

impl Default for ContentBlock {
    fn default() -> Self {
        ContentBlock::Empty(Empty::default())
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CreateMessageRequestParam {
    #[serde(rename = "includeContext")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub include_context: Option<CreateMessageRequestParamIncludeContext>,

    /// Maximum tokens to sample
    #[serde(rename = "maxTokens")]
    pub max_tokens: i64,

    /// Conversation messages of of TextContent, ImageContent or AudioContent
    #[serde(rename = "messages")]
    pub messages: Vec<SamplingMessage>,

    /// Preferences for model selection
    #[serde(rename = "modelPreferences")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub model_preferences: Option<ModelPreferences>,

    /// Stop sequences
    #[serde(rename = "stopSequences")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub stop_sequences: Option<Vec<String>>,

    /// Optional system prompt
    #[serde(rename = "systemPrompt")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub system_prompt: Option<String>,

    /// Sampling temperature
    #[serde(rename = "temperature")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub temperature: Option<f64>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum CreateMessageRequestParamIncludeContext {
    #[default]
    #[serde(rename = "none")]
    None,
    #[serde(rename = "thisServer")]
    ThisServer,
    #[serde(rename = "allServers")]
    AllServers,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct CreateMessageResult {
    /// One of TextContent, ImageContent or AudioContent
    #[serde(rename = "content")]
    pub content: CreateMessageResultContent,

    /// Name of the model used
    #[serde(rename = "model")]
    pub model: String,

    #[serde(rename = "role")]
    pub role: Role,

    /// Optional reason sampling stopped
    #[serde(rename = "stopReason")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub stop_reason: Option<String>,
}

type CreateMessageResultContent = SamplingMessage;

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ElicitRequestParamWithTimeout {
    /// Message to present to the user
    #[serde(rename = "message")]
    pub message: String,

    #[serde(rename = "requestedSchema")]
    pub requested_schema: Schema,

    /// Optional timeout in milliseconds
    #[serde(rename = "timeout")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub timeout: Option<i64>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ElicitResult {
    #[serde(rename = "action")]
    pub action: ElicitResultAction,

    /// Form data submitted by user (only present when action is accept)
    #[serde(rename = "content")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub content: Option<HashMap<String, ElicitResultContentValue>>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum ElicitResultAction {
    #[default]
    #[serde(rename = "accept")]
    Accept,
    #[serde(rename = "decline")]
    Decline,
    #[serde(rename = "cancel")]
    Cancel,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
#[serde(untagged)]
pub enum ElicitResultContentValue {
    String(String),
    Number(Number), // or serde_json::Number if you want exactness
    Bool(bool),
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct EmbeddedResource {
    /// Optional additional metadata about the embedded resource
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// Optional resource annotations
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// The embedded TextResourceContents or BlobResourceContents
    #[serde(rename = "resource")]
    pub resource: ResourceContents,

    #[serde(rename = "type")]
    pub r#type: ResourceType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Empty {}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct EnumSchema {
    /// Description of the enum input
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// Array of allowed string values
    #[serde(rename = "enum")]
    pub r#enum: Vec<String>,

    /// Optional array of human-readable names for the enum values
    #[serde(rename = "enumNames")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub enum_names: Option<Vec<String>>,

    /// Optional human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    #[serde(rename = "type")]
    pub r#type: StringType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct GetPromptRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,

    #[serde(rename = "request")]
    pub request: GetPromptRequestParam,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct GetPromptRequestParam {
    /// Arguments for templating the prompt
    #[serde(rename = "arguments")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub arguments: Option<HashMap<String, String>>,

    /// Name of the prompt to retrieve
    #[serde(rename = "name")]
    pub name: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct GetPromptResult {
    /// Optional description of the prompt
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// Array of prompt messages
    #[serde(rename = "messages")]
    pub messages: Vec<PromptMessage>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ImageContent {
    /// Optional additional metadata about the content block
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// Optional content annotations
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// Base64-encoded image data
    #[serde(rename = "data")]
    pub data: String,

    /// MIME type of the image (e.g. 'image/png')
    #[serde(rename = "mimeType")]
    pub mime_type: String,

    #[serde(rename = "type")]
    pub r#type: ImageType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum ImageType {
    #[default]
    #[serde(rename = "image")]
    Image,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListPromptsRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListPromptsResult {
    /// Array of available prompts
    #[serde(rename = "prompts")]
    pub prompts: Vec<Prompt>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListResourcesRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListResourcesResult {
    /// Array of available resources
    #[serde(rename = "resources")]
    pub resources: Vec<Resource>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListResourceTemplatesRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListResourceTemplatesResult {
    /// Array of resource templates
    #[serde(rename = "resourceTemplates")]
    pub resource_templates: Vec<ResourceTemplate>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListRootsResult {
    /// Array of root directories/resources
    #[serde(rename = "roots")]
    pub roots: Vec<Root>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListToolsRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ListToolsResult {
    /// Array of available tools
    #[serde(rename = "tools")]
    pub tools: Vec<Tool>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum LoggingLevel {
    #[default]
    #[serde(rename = "debug")]
    Debug,
    #[serde(rename = "info")]
    Info,
    #[serde(rename = "notice")]
    Notice,
    #[serde(rename = "warning")]
    Warning,
    #[serde(rename = "error")]
    Error,
    #[serde(rename = "critical")]
    Critical,
    #[serde(rename = "alert")]
    Alert,
    #[serde(rename = "emergency")]
    Emergency,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct LoggingMessageNotificationParam {
    /// Data to log (any JSON-serializable type)
    #[serde(rename = "data")]
    pub data: Value,

    #[serde(rename = "level")]
    pub level: LoggingLevel,

    /// Optional logger name
    #[serde(rename = "logger")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub logger: Option<String>,
}

type Meta = Map<String, Value>;

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ModelHint {
    /// Suggested model name or family
    #[serde(rename = "name")]
    pub name: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ModelPreferences {
    /// Priority for cost (0-1)
    #[serde(rename = "costPriority")]
    pub cost_priority: f32,

    /// Model name hints
    #[serde(rename = "hints")]
    pub hints: Vec<ModelHint>,

    /// Priority for intelligence (0-1)
    #[serde(rename = "intelligencePriority")]
    pub intelligence_priority: f32,

    /// Priority for speed (0-1)
    #[serde(rename = "speedPriority")]
    pub speed_priority: f32,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct NumberSchema {
    /// Description of the number input
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// Maximum value
    #[serde(rename = "maximum")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub maximum: Option<f64>,

    /// Minimum value
    #[serde(rename = "minimum")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub minimum: Option<f64>,

    /// Optional human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    #[serde(rename = "type")]
    pub r#type: NumberType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum NumberType {
    #[default]
    #[serde(rename = "number")]
    Number,
    #[serde(rename = "integer")]
    Integer,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum ObjectType {
    #[default]
    #[serde(rename = "object")]
    Object,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct PluginNotificationContext {
    /// Additional metadata about the notification
    #[serde(rename = "meta")]
    pub meta: Meta,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct PluginRequestContext {
    /// Additional metadata about the request
    #[serde(rename = "_meta")]
    pub meta: Meta,

    /// Unique identifier for this request
    #[serde(rename = "id")]
    pub id: PluginRequestId,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
#[serde(untagged)]
pub enum PluginRequestId {
    String(String),
    Number(i64),
}

impl Default for PluginRequestId {
    fn default() -> Self {
        PluginRequestId::String(String::new())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
#[serde(untagged)]
pub enum PrimitiveSchemaDefinition {
    Boolean(BooleanSchema),
    Enum(EnumSchema),
    Number(NumberSchema),
    String(StringSchema),
    Empty(Empty),
}

impl Default for PrimitiveSchemaDefinition {
    fn default() -> Self {
        PrimitiveSchemaDefinition::Empty(Empty::default())
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ProgressNotificationParam {
    /// Optional progress message describing current operation
    #[serde(rename = "message")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub message: Option<String>,

    /// The progress thus far
    #[serde(rename = "progress")]
    pub progress: f64,

    /// A token identifying the progress context
    #[serde(rename = "progressToken")]
    pub progress_token: String,

    /// Optional total units of work
    #[serde(rename = "total")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub total: Option<f64>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct Prompt {
    /// Optional prompt arguments
    #[serde(rename = "arguments")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub arguments: Option<Vec<PromptArgument>>,

    /// Description of what the prompt does
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// Unique name of the prompt
    #[serde(rename = "name")]
    pub name: String,

    /// Human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct PromptArgument {
    /// Description of the argument
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// Name of the argument
    #[serde(rename = "name")]
    pub name: String,

    /// Whether this argument is required
    #[serde(rename = "required")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub required: Option<bool>,

    /// Human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct PromptMessage {
    /// One of TextContent, ImageContent, AudioContent, EmbeddedResource, or ResourceLink
    #[serde(rename = "content")]
    pub content: ContentBlock,

    #[serde(rename = "role")]
    pub role: Role,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct PromptReference {
    /// Name of the prompt
    #[serde(rename = "name")]
    pub name: String,

    /// Optional human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    #[serde(rename = "type")]
    pub r#type: PromptReferenceType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum PromptReferenceType {
    #[default]
    #[serde(rename = "prompt")]
    Prompt,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ReadResourceRequest {
    #[serde(rename = "context")]
    pub context: PluginRequestContext,

    #[serde(rename = "request")]
    pub request: ReadResourceRequestParam,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ReadResourceRequestParam {
    /// URI of the resource to read
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ReadResourceResult {
    /// Array of TextResourceContents or BlobResourceContents
    #[serde(rename = "contents")]
    pub contents: Vec<ResourceContents>,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
#[serde(untagged)]
pub enum Reference {
    Prompt(PromptReference),
    ResourceTemplate(ResourceTemplateReference),
    Empty(Empty),
}

impl Default for Reference {
    fn default() -> Self {
        Reference::Empty(Empty::default())
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct Resource {
    /// Optional resource annotations
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// Description of the resource
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// MIME type of the resource
    #[serde(rename = "mimeType")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub mime_type: Option<String>,

    /// Human-readable name
    #[serde(rename = "name")]
    pub name: String,

    /// Size in bytes
    #[serde(rename = "size")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub size: Option<i64>,

    /// Human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    /// URI of the resource
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
#[serde(untagged)]
pub enum ResourceContents {
    Blob(BlobResourceContents),
    Text(TextResourceContents),
    Empty(Empty),
}

impl Default for ResourceContents {
    fn default() -> Self {
        ResourceContents::Empty(Empty::default())
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ResourceLink {
    /// Optional additional metadata about the resource link
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// Optional resource annotations
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// Optional description of the resource
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// Optional MIME type of the resource
    #[serde(rename = "mimeType")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub mime_type: Option<String>,

    /// Optional human-readable name
    #[serde(rename = "name")]
    pub name: String,

    /// Optional size in bytes
    #[serde(rename = "size")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub size: Option<i64>,

    /// Optional human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    #[serde(rename = "type")]
    pub r#type: ResourceLinkType,

    /// URI of the resource
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum ResourceLinkType {
    #[default]
    #[serde(rename = "resource_link")]
    ResourceLink,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum ResourceReferenceType {
    #[default]
    #[serde(rename = "resource")]
    Resource,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ResourceTemplate {
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// Description of the template
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    /// MIME type for resources matching this template
    #[serde(rename = "mimeType")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub mime_type: Option<String>,

    /// Human-readable name
    #[serde(rename = "name")]
    pub name: String,

    /// Human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    /// RFC 6570 URI template pattern
    #[serde(rename = "uriTemplate")]
    pub uri_template: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ResourceTemplateReference {
    #[serde(rename = "type")]
    pub r#type: ResourceReferenceType,

    /// URI or URI template pattern of the resource
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum ResourceType {
    #[default]
    #[serde(rename = "resource")]
    Resource,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ResourceUpdatedNotificationParam {
    /// URI of the updated resource
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum Role {
    #[default]
    #[serde(rename = "assistant")]
    Assistant,
    #[serde(rename = "user")]
    User,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct Root {
    /// Optional human-readable name
    #[serde(rename = "name")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub name: Option<String>,

    /// URI of the root (typically file://)
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
#[serde(untagged)]
pub enum SamplingMessage {
    Audio(AudioContent),
    Image(ImageContent),
    Text(TextContent),
    Empty(Empty),
}

impl Default for SamplingMessage {
    fn default() -> Self {
        SamplingMessage::Empty(Empty::default())
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct Schema {
    /// A map of StringSchema, NumberSchema, BooleanSchema or EnumSchema definitions (no nesting)
    #[serde(rename = "properties")]
    pub properties: HashMap<String, PrimitiveSchemaDefinition>,

    /// Required property names
    #[serde(rename = "required")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub required: Option<Vec<String>>,

    #[serde(rename = "type")]
    pub r#type: ObjectType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct StringSchema {
    /// Description of the string input
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    #[serde(rename = "format")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub format: Option<StringSchemaFormat>,

    /// Maximum length of the string
    #[serde(rename = "maxLength")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub max_length: Option<i64>,

    /// Minimum length of the string
    #[serde(rename = "minLength")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub min_length: Option<i64>,

    /// Optional human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,

    #[serde(rename = "type")]
    pub r#type: StringType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum StringSchemaFormat {
    #[default]
    #[serde(rename = "email")]
    Email,
    #[serde(rename = "uri")]
    Uri,
    #[serde(rename = "date")]
    Date,
    #[serde(rename = "date_time")]
    Datetime,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum StringType {
    #[default]
    #[serde(rename = "string")]
    String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct TextContent {
    /// Optional additional metadata about the content block
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// Optional content annotations
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// The text content
    #[serde(rename = "text")]
    pub text: String,

    #[serde(rename = "type")]
    pub r#type: TextType,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct TextResourceContents {
    /// Optional additional metadata about the text resource
    #[serde(rename = "_meta")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub meta: Option<Meta>,

    /// MIME type of the text content (e.g. 'text/plain')
    #[serde(rename = "mimeType")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub mime_type: Option<String>,

    /// Text content of the resource
    #[serde(rename = "text")]
    pub text: String,

    /// URI of the resource
    #[serde(rename = "uri")]
    pub uri: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub enum TextType {
    #[default]
    #[serde(rename = "text")]
    Text,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct Tool {
    /// Optional tool annotations
    #[serde(rename = "annotations")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub annotations: Option<Annotations>,

    /// Description of what the tool does
    #[serde(rename = "description")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub description: Option<String>,

    #[serde(rename = "inputSchema")]
    pub input_schema: ToolSchema,

    /// Unique name of the tool
    #[serde(rename = "name")]
    pub name: String,

    #[serde(rename = "outputSchema")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub output_schema: Option<ToolSchema>,

    /// Human-readable title
    #[serde(rename = "title")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub title: Option<String>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, FromBytes, ToBytes)]
#[encoding(Json)]
pub struct ToolSchema {
    /// Schema properties
    #[serde(rename = "properties")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub properties: Option<Map<String, Value>>,

    /// Required properties
    #[serde(rename = "required")]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub required: Option<Vec<String>>,

    #[serde(rename = "type")]
    pub r#type: ObjectType,
}

```
Page 4/8FirstPrevNextLast