This is page 3 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/gomodule/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/hash/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/maven/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/memory/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/myip/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/serper/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/sqlite/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/think/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qr-code/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
// THIS FILE WAS GENERATED BY `xtp-rust-bindgen`. DO NOT EDIT.
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/time/src/pdk.rs:
--------------------------------------------------------------------------------
```rust
// THIS FILE WAS GENERATED BY `xtp-rust-bindgen`. DO NOT EDIT.
#![allow(non_snake_case)]
#![allow(unused_macros)]
use extism_pdk::*;
#[allow(unused)]
fn panic_if_key_missing() -> ! {
panic!("missing key");
}
pub(crate) mod internal {
pub(crate) fn return_error(e: extism_pdk::Error) -> i32 {
let err = format!("{:?}", e);
let mem = extism_pdk::Memory::from_bytes(&err).unwrap();
unsafe {
extism_pdk::extism::error_set(mem.offset());
}
-1
}
}
#[allow(unused)]
macro_rules! try_input {
() => {{
let x = extism_pdk::input();
match x {
Ok(x) => x,
Err(e) => return internal::return_error(e),
}
}};
}
#[allow(unused)]
macro_rules! try_input_json {
() => {{
let x = extism_pdk::input();
match x {
Ok(extism_pdk::Json(x)) => x,
Err(e) => return internal::return_error(e),
}
}};
}
use base64_serde::base64_serde_type;
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn call() -> i32 {
let ret =
crate::call(try_input_json!()).and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn describe() -> i32 {
let ret = crate::describe().and_then(|x| extism_pdk::output(extism_pdk::Json(x)));
match ret {
Ok(()) => 0,
Err(e) => internal::return_error(e),
}
}
}
pub mod types {
use super::*;
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct BlobResourceContents {
/// A base64-encoded string representing the binary data of the item.
#[serde(rename = "blob")]
pub blob: String,
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolRequest {
#[serde(rename = "method")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub method: Option<String>,
#[serde(rename = "params")]
pub params: types::Params,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct CallToolResult {
#[serde(rename = "content")]
pub content: Vec<types::Content>,
/// Whether the tool call ended in an error.
///
/// If not set, this is assumed to be false (the call was successful).
#[serde(rename = "isError")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub is_error: Option<bool>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Content {
#[serde(rename = "annotations")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub annotations: Option<types::TextAnnotation>,
/// The base64-encoded image data.
#[serde(rename = "data")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub data: Option<String>,
/// The MIME type of the image. Different providers may support different image types.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text content of the message.
#[serde(rename = "text")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub text: Option<String>,
#[serde(rename = "type")]
pub r#type: types::ContentType,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum ContentType {
#[default]
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "resource")]
Resource,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ListToolsResult {
/// The list of ToolDescription objects provided by this servlet.
#[serde(rename = "tools")]
pub tools: Vec<types::ToolDescription>,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct Params {
#[serde(rename = "arguments")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub arguments: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(rename = "name")]
pub name: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub enum Role {
#[default]
#[serde(rename = "assistant")]
Assistant,
#[serde(rename = "user")]
User,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextAnnotation {
/// Describes who the intended customer of this object or data is.
///
/// It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
#[serde(rename = "audience")]
pub audience: Vec<types::Role>,
/// Describes how important this data is for operating the server.
///
/// A value of 1 means "most important," and indicates that the data is
/// effectively required, while 0 means "least important," and indicates that
/// the data is entirely optional.
#[serde(rename = "priority")]
pub priority: f32,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct TextResourceContents {
/// The MIME type of this resource, if known.
#[serde(rename = "mimeType")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mime_type: Option<String>,
/// The text of the item. This must only be set if the item can actually be represented as text (not binary data).
#[serde(rename = "text")]
pub text: String,
/// The URI of this resource.
#[serde(rename = "uri")]
pub uri: String,
}
#[derive(
Default,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
extism_pdk::FromBytes,
extism_pdk::ToBytes,
)]
#[encoding(Json)]
pub struct ToolDescription {
/// A description of the tool
#[serde(rename = "description")]
pub description: String,
/// The JSON schema describing the argument input
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Map<String, serde_json::Value>,
/// The name of the tool. It should match the plugin / binding name.
#[serde(rename = "name")]
pub name: String,
}
}
mod raw_imports {
use super::*;
#[host_fn]
extern "ExtismHost" {}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/qdrant/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
mod qdrant_client;
use extism_pdk::*;
use pdk::types::{
CallToolRequest, CallToolResult, Content, ContentType, ListToolsResult, ToolDescription,
};
use qdrant_client::*;
use serde_json::json;
fn get_qdrant_client() -> Result<QdrantClient, Error> {
let qdrant_url = config::get("QDRANT_URL")?
.ok_or_else(|| Error::msg("QDRANT_URL configuration is required but not set"))?;
let mut client = QdrantClient::new_with_url(qdrant_url);
// Check if API key is set in config
if let Ok(Some(api_key)) = config::get("QDRANT_API_KEY") {
client.set_api_key(api_key);
}
Ok(client)
}
fn ensure_collection_exists(
client: &QdrantClient,
collection_name: &str,
vector_size: u32,
) -> Result<(), Error> {
// check if the collection exists. If present, delete it.
if let Ok(true) = client.collection_exists(collection_name) {
println!("Collection `{}` exists", collection_name);
match client.delete_collection(collection_name) {
Ok(_) => println!("Collection `{}` deleted", collection_name),
Err(e) => println!("Error deleting collection: {:?}", e),
}
};
// Create collection
let create_result = client.create_collection(collection_name, vector_size);
println!("Create collection result is {:?}", create_result);
Ok(())
}
pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
match input.params.name.as_str() {
"qdrant_store" => qdrant_store(input),
"qdrant_find" => qdrant_find(input),
"qdrant_create_collection" => qdrant_create_collection(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 qdrant_store(input: CallToolRequest) -> Result<CallToolResult, Error> {
let args = input.params.arguments.unwrap_or_default();
let collection_name = args
.get("collection_name")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::msg("collection_name parameter is required"))?;
let vector = args
.get("vector")
.and_then(|v| v.as_array())
.ok_or_else(|| Error::msg("vector parameter is required"))?
.iter()
.map(|v| v.as_f64().unwrap_or_default())
.collect::<Vec<f64>>();
let text = args
.get("text")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::msg("text parameter is required"))?;
let client = get_qdrant_client()?;
ensure_collection_exists(&client, collection_name, vector.len() as u32)?;
let point_id = uuid::Uuid::new_v4().to_string();
let vector: Vec<f32> = vector.into_iter().map(|x| x as f32).collect();
let mut points = Vec::new();
points.push(Point {
id: PointId::Uuid(point_id.clone()),
vector,
payload: json!({
"text": text,
"metadata": {},
})
.as_object()
.map(|m| m.to_owned()),
});
client.upsert_points(collection_name, points)?;
println!("Upsert points result is {:?}", ());
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(format!(
"Successfully stored document with ID: {}",
point_id
)),
mime_type: None,
r#type: ContentType::Text,
data: None,
}],
})
}
fn qdrant_find(input: CallToolRequest) -> Result<CallToolResult, Error> {
let args = input.params.arguments.unwrap_or_default();
let collection_name = args
.get("collection_name")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::msg("collection_name parameter is required"))?;
let vector = args
.get("vector")
.and_then(|v| v.as_array())
.ok_or_else(|| Error::msg("vector parameter is required"))?
.iter()
.map(|v| v.as_f64().unwrap_or_default())
.collect::<Vec<f64>>();
let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(5);
let client = get_qdrant_client()?;
let vector_f32: Vec<f32> = vector.into_iter().map(|x| x as f32).collect();
let search_result = client.search_points(collection_name, vector_f32, limit, None)?;
let mut results = Vec::new();
for point in search_result {
if let Some(payload) = &point.payload {
if let Some(text) = payload.get("text").and_then(|v| v.as_str()) {
results.push(format!("Score: {:.4} - {}", point.score, text));
}
}
}
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(results.join("\n")),
mime_type: None,
r#type: ContentType::Text,
data: None,
}],
})
}
fn qdrant_create_collection(input: CallToolRequest) -> Result<CallToolResult, Error> {
let args = input.params.arguments.unwrap_or_default();
let collection_name = args
.get("collection_name")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::msg("collection_name parameter is required"))?;
let vector_size = args
.get("vector_size")
.and_then(|v| v.as_u64())
.unwrap_or(384) as u32;
let client = get_qdrant_client()?;
ensure_collection_exists(&client, collection_name, vector_size)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(format!(
"Successfully created collection '{}' with vector size {}",
collection_name, vector_size
)),
mime_type: None,
r#type: ContentType::Text,
data: None,
}],
})
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult {
tools: vec![
ToolDescription {
name: "qdrant_create_collection".into(),
description: "Creates a new collection in Qdrant with specified vector size".into(),
input_schema: json!({
"type": "object",
"properties": {
"collection_name": {
"type": "string",
"description": "The name of the collection to create",
},
"vector_size": {
"type": "integer",
"description": "The size of vectors to be stored in this collection",
"default": 384
}
},
"required": ["collection_name"],
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "qdrant_store".into(),
description: "Stores a document with its vector embedding in Qdrant.".into(),
input_schema: json!({
"type": "object",
"properties": {
"collection_name": {
"type": "string",
"description": "The name of the collection to store the document in",
},
"text": {
"type": "string",
"description": "The text content to store",
},
"vector": {
"type": "array",
"items": {"type": "number"},
"description": "The vector embedding of the text.",
}
},
"required": ["collection_name", "text", "vector"],
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "qdrant_find".into(),
description: "Finds similar documents in Qdrant using vector similarity search"
.into(),
input_schema: json!({
"type": "object",
"properties": {
"collection_name": {
"type": "string",
"description": "The name of the collection to search in",
},
"vector": {
"type": "array",
"items": {"type": "number"},
"description": "The query vector to search with.",
},
"limit": {
"type": "integer",
"description": "Maximum number of results to return",
"default": 5
}
},
"required": ["collection_name", "vector"],
})
.as_object()
.unwrap()
.clone(),
},
],
})
}
```
--------------------------------------------------------------------------------
/src/https_auth.rs:
--------------------------------------------------------------------------------
```rust
use crate::config::AuthConfig;
use reqwest::RequestBuilder;
use std::{cmp::Reverse, collections::HashMap};
use url::Url;
pub trait Authenticator {
/// Adds authentication headers to the request if present in auths.
fn add_auth(self, auths: &Option<HashMap<Url, AuthConfig>>, url: &Url) -> RequestBuilder;
}
impl Authenticator for RequestBuilder {
fn add_auth(self, auths: &Option<HashMap<Url, AuthConfig>>, url: &Url) -> RequestBuilder {
if let Some(auths) = auths {
let mut auths: Vec<(&str, &AuthConfig)> =
auths.iter().map(|(k, v)| (k.as_str(), v)).collect();
auths.sort_by_key(|c| Reverse(c.0.len()));
let url = url.to_string();
for (k, v) in auths {
if url.starts_with(k) {
return match v {
AuthConfig::Basic { username, password } => {
self.basic_auth(username, Some(password))
}
AuthConfig::Token { token } => self.bearer_auth(token),
};
}
}
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use reqwest::Client;
use std::collections::HashMap;
use url::Url;
#[test]
fn test_add_auth_basic_authentication() {
let client = Client::new();
let mut auths = HashMap::new();
let url = Url::parse("https://api.example.com").unwrap();
auths.insert(
url.clone(),
AuthConfig::Basic {
username: "testuser".to_string(),
password: "testpass".to_string(),
},
);
let request = client.get("https://api.example.com/endpoint");
let authenticated_request = request.add_auth(&Some(auths), &url);
// We can't easily test the actual header since reqwest doesn't expose it,
// but we can verify the method doesn't panic and returns a RequestBuilder
// The fact that we got here without panicking means the method worked
drop(authenticated_request);
}
#[test]
fn test_add_auth_token_authentication() {
let client = Client::new();
let mut auths = HashMap::new();
let url = Url::parse("https://api.example.com").unwrap();
auths.insert(
url.clone(),
AuthConfig::Token {
token: "bearer-token-123".to_string(),
},
);
let request = client.get("https://api.example.com/endpoint");
let authenticated_request = request.add_auth(&Some(auths), &url);
// Verify the method completes without error
// The fact that we got here without panicking means the method worked
drop(authenticated_request);
}
#[test]
fn test_add_auth_no_auths_provided() {
let client = Client::new();
let url = Url::parse("https://api.example.com").unwrap();
let request = client.get("https://api.example.com/endpoint");
let result_request = request.add_auth(&None, &url);
// Should return the original request unchanged
// The fact that we got here without panicking means the method worked
drop(result_request);
}
#[test]
fn test_add_auth_empty_auths_map() {
let client = Client::new();
let auths = HashMap::new();
let url = Url::parse("https://api.example.com").unwrap();
let request = client.get("https://api.example.com/endpoint");
let result_request = request.add_auth(&Some(auths), &url);
// Should return the original request unchanged when no matching auth
// The fact that we got here without panicking means the method worked
drop(result_request);
}
#[test]
fn test_add_auth_url_prefix_matching() {
let client = Client::new();
let mut auths = HashMap::new();
// Add auth for broader domain
let domain_url = Url::parse("https://example.com").unwrap();
auths.insert(
domain_url,
AuthConfig::Basic {
username: "domain_user".to_string(),
password: "domain_pass".to_string(),
},
);
// Add auth for specific API endpoint (longer prefix)
let api_url = Url::parse("https://example.com/api").unwrap();
auths.insert(
api_url,
AuthConfig::Token {
token: "api-token".to_string(),
},
);
// Test that longer prefix wins
let target_url = Url::parse("https://example.com/api/v1/data").unwrap();
let request = client.get(target_url.as_str());
let authenticated_request = request.add_auth(&Some(auths), &target_url);
// The API token should be used (longest prefix)
// The fact that we got here without panicking means the method worked
drop(authenticated_request);
}
#[test]
fn test_add_auth_url_no_match() {
let client = Client::new();
let mut auths = HashMap::new();
let auth_url = Url::parse("https://api.example.com").unwrap();
auths.insert(
auth_url,
AuthConfig::Basic {
username: "testuser".to_string(),
password: "testpass".to_string(),
},
);
// Request to different domain
let target_url = Url::parse("https://different.com/endpoint").unwrap();
let request = client.get(target_url.as_str());
let result_request = request.add_auth(&Some(auths), &target_url);
// Should return the original request unchanged when no URL match
// The fact that we got here without panicking means the method worked
drop(result_request);
}
#[test]
fn test_add_auth_multiple_auths_longest_prefix_wins() {
let client = Client::new();
let mut auths = HashMap::new();
// Add multiple auths with different prefix lengths
auths.insert(
Url::parse("https://example.com").unwrap(),
AuthConfig::Basic {
username: "broad_user".to_string(),
password: "broad_pass".to_string(),
},
);
auths.insert(
Url::parse("https://example.com/api").unwrap(),
AuthConfig::Token {
token: "api_token".to_string(),
},
);
auths.insert(
Url::parse("https://example.com/api/v1").unwrap(),
AuthConfig::Basic {
username: "v1_user".to_string(),
password: "v1_pass".to_string(),
},
);
// Test with URL that matches all three (longest should win)
let target_url = Url::parse("https://example.com/api/v1/endpoint").unwrap();
let request = client.get(target_url.as_str());
let authenticated_request = request.add_auth(&Some(auths), &target_url);
// Should use the v1 auth (longest prefix)
// The fact that we got here without panicking means the method worked
drop(authenticated_request);
}
#[test]
fn test_add_auth_exact_url_match() {
let client = Client::new();
let mut auths = HashMap::new();
let exact_url = Url::parse("https://api.example.com/v1/data").unwrap();
auths.insert(
exact_url.clone(),
AuthConfig::Token {
token: "exact-match-token".to_string(),
},
);
let request = client.get(exact_url.as_str());
let authenticated_request = request.add_auth(&Some(auths), &exact_url);
// The fact that we got here without panicking means the method worked
drop(authenticated_request);
}
#[test]
fn test_add_auth_case_sensitive_urls() {
let client = Client::new();
let mut auths = HashMap::new();
let auth_url = Url::parse("https://API.EXAMPLE.COM").unwrap();
auths.insert(
auth_url,
AuthConfig::Basic {
username: "testuser".to_string(),
password: "testpass".to_string(),
},
);
// Test with lowercase URL
let target_url = Url::parse("https://api.example.com/endpoint").unwrap();
let request = client.get(target_url.as_str());
let result_request = request.add_auth(&Some(auths), &target_url);
// Should not match due to case sensitivity
// The fact that we got here without panicking means the method worked
drop(result_request);
}
#[test]
fn test_auth_config_types_comprehensive() {
// Test all AuthConfig variants can be created and used
let basic_auth = AuthConfig::Basic {
username: "basic_user".to_string(),
password: "basic_pass".to_string(),
};
let token_auth = AuthConfig::Token {
token: "token_value".to_string(),
};
let client = Client::new();
let url = Url::parse("https://test.com").unwrap();
// Test both types can be used with add_auth
let mut auths = HashMap::new();
auths.insert(url.clone(), basic_auth);
let request1 = client.get(url.as_str());
let result1 = request1.add_auth(&Some(auths), &url);
// The fact that we got here without panicking means the method worked
drop(result1);
let mut auths = HashMap::new();
auths.insert(url.clone(), token_auth);
let request2 = client.get(url.as_str());
let result2 = request2.add_auth(&Some(auths), &url);
// The fact that we got here without panicking means the method worked
drop(result2);
}
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/sqlite/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod pdk;
use extism_pdk::*;
use pdk::types::{
CallToolRequest, CallToolResult, Content, ContentType, ListToolsResult, ToolDescription,
};
use rusqlite::Connection;
use serde_json::json;
use std::sync::Once;
static DB_INIT: Once = Once::new();
fn init_db(db_path: &str) -> Result<(), Error> {
let _conn = Connection::open_with_flags(
db_path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_CREATE,
)?;
Ok(())
}
fn get_db_path() -> Result<String, Error> {
config::get("db_path")?
.ok_or_else(|| Error::msg("db_path configuration is required but not set"))
}
fn execute_read_query(query: &str, db_path: &str) -> Result<String, Error> {
let conn = Connection::open_with_flags(db_path, rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE)?;
let mut stmt = conn.prepare(query)?;
let column_names: Vec<String> = stmt.column_names().into_iter().map(String::from).collect();
let rows = stmt.query_map([], |row| {
let mut map = serde_json::Map::new();
for (i, col_name) in column_names.iter().enumerate() {
let value = match row.get_ref(i)? {
rusqlite::types::ValueRef::Null => serde_json::Value::Null,
rusqlite::types::ValueRef::Integer(i) => json!(i),
rusqlite::types::ValueRef::Real(f) => json!(f),
rusqlite::types::ValueRef::Text(s) => json!(s),
rusqlite::types::ValueRef::Blob(b) => json!(b),
};
map.insert(col_name.clone(), value);
}
Ok(map)
})?;
let results: Vec<serde_json::Map<String, serde_json::Value>> =
rows.filter_map(Result::ok).collect();
Ok(serde_json::to_string(&results)?)
}
fn execute_write_query(query: &str, db_path: &str) -> Result<String, Error> {
let conn = Connection::open_with_flags(db_path, rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE)?;
let affected = conn.execute(query, [])?;
Ok(json!({ "rows_affected": affected }).to_string())
}
fn create_table(query: &str, db_path: &str) -> Result<String, Error> {
let conn = Connection::open_with_flags(db_path, rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE)?;
conn.execute(query, [])?;
Ok(json!({ "status": "success" }).to_string())
}
fn list_tables(db_path: &str) -> Result<String, Error> {
let conn = Connection::open_with_flags(db_path, rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE)?;
let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table'")?;
let tables: Result<Vec<String>, _> = stmt.query_map([], |row| row.get(0))?.collect();
Ok(json!({ "tables": tables? }).to_string())
}
fn describe_table(table_name: &str, db_path: &str) -> Result<String, Error> {
let conn = Connection::open_with_flags(db_path, rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE)?;
let mut stmt = conn.prepare(&format!("PRAGMA table_info({})", table_name))?;
let columns = stmt.query_map([], |row| {
Ok(json!({
"cid": row.get::<_, i64>(0)?,
"name": row.get::<_, String>(1)?,
"type": row.get::<_, String>(2)?,
"notnull": row.get::<_, bool>(3)?,
"dflt_value": row.get::<_, Option<String>>(4)?,
"pk": row.get::<_, bool>(5)?
}))
})?;
let schema: Vec<serde_json::Value> = columns.filter_map(Result::ok).collect();
Ok(json!({ "schema": schema }).to_string())
}
pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
let db_path = get_db_path()?;
DB_INIT.call_once(|| {
init_db(&db_path).expect("Failed to initialize database");
});
match input.params.name.as_str() {
"sqlite_read_query" => {
let args = input.params.arguments.unwrap_or_default();
let query = match args.get("query") {
Some(v) if v.is_string() => v.as_str().unwrap(),
_ => return Err(Error::msg("query parameter is required")),
};
let result = execute_read_query(query, &db_path)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(result),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
"sqlite_write_query" => {
let args = input.params.arguments.unwrap_or_default();
let query = match args.get("query") {
Some(v) if v.is_string() => v.as_str().unwrap(),
_ => return Err(Error::msg("query parameter is required")),
};
let result = execute_write_query(query, &db_path)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(result),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
"sqlite_create_table" => {
let args = input.params.arguments.unwrap_or_default();
let query = match args.get("query") {
Some(v) if v.is_string() => v.as_str().unwrap(),
_ => return Err(Error::msg("query parameter is required")),
};
let result = create_table(query, &db_path)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(result),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
"sqlite_list_tables" => {
let result = list_tables(&db_path)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(result),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
"sqlite_describe_table" => {
let args = input.params.arguments.unwrap_or_default();
let table_name = match args.get("table_name") {
Some(v) if v.is_string() => v.as_str().unwrap(),
_ => return Err(Error::msg("table_name parameter is required")),
};
let result = describe_table(table_name, &db_path)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(result),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
_ => 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,
}],
}),
}
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult {
tools: vec![
ToolDescription {
name: "sqlite_read_query".into(),
description: "Execute a SELECT query on the SQLite database".into(),
input_schema: json!({
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "SELECT SQL query to execute",
}
},
"required": ["query"],
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "sqlite_write_query".into(),
description: "Execute an INSERT, UPDATE, or DELETE query on the SQLite database"
.into(),
input_schema: json!({
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "SQL query to execute",
}
},
"required": ["query"],
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "sqlite_create_table".into(),
description: "Create a new table in the SQLite database".into(),
input_schema: json!({
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "CREATE TABLE SQL statement",
}
},
"required": ["query"],
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "sqlite_list_tables".into(),
description: "List all tables in the SQLite database".into(),
input_schema: json!({
"type": "object",
"properties": {},
"required": [],
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "sqlite_describe_table".into(),
description: "Get the schema information for a specific table".into(),
input_schema: json!({
"type": "object",
"properties": {
"table_name": {
"type": "string",
"description": "Name of the table to describe",
}
},
"required": ["table_name"],
})
.as_object()
.unwrap()
.clone(),
},
],
})
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/github/branches.go:
--------------------------------------------------------------------------------
```go
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/extism/go-pdk"
)
var (
CreateBranchTool = ToolDescription{
Name: "gh-create-branch",
Description: "Create a branch in 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"),
"from_branch": prop("string", "Source branch (defaults to `main` if not provided)"),
},
"required": []string{"owner", "repo", "branch", "from_branch"},
},
}
ListPullRequestsTool = ToolDescription{
Name: "gh-list-pull-requests",
Description: "Lists pull requests in a specified repository. Supports different response formats via accept parameter.",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The account owner of the repository. The name is not case sensitive."),
"repo": prop("string", "The name of the repository without the .git extension. The name is not case sensitive."),
"state": prop("string", "Either open, closed, or all to filter by state."),
"head": prop("string", "Filter pulls by head user or head organization and branch name in the format of user:ref-name or organization:ref-name."),
"base": prop("string", "Filter pulls by base branch name. Example: gh-pages"),
"sort": prop("string", "What to sort results by. Can be one of: created, updated, popularity, long-running"),
"direction": prop("string", "The direction of the sort. Default: desc when sort is created or not specified, otherwise asc"),
"per_page": prop("integer", "The number of results per page (max 100)"),
"page": prop("integer", "The page number of the results to fetch"),
"accept": prop("string", "Response format: raw (default), text, html, or full. Raw returns body, text returns body_text, html returns body_html, full returns all."),
},
"required": []string{"owner", "repo"},
},
}
CreatePullRequestTool = ToolDescription{
Name: "gh-create-pull-request",
Description: "Create a pull request in a GitHub repository",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"title": prop("string", "The title of the pull request"),
"body": prop("string", "The body of the pull request"),
"head": prop("string", "The branch you want to merge into the base branch"),
"base": prop("string", "The branch you want to merge into"),
"draft": prop("boolean", "Create as draft (optional)"),
"maintainer_can_modify": prop("boolean", "Allow maintainers to modify the pull request"),
},
"required": []string{"owner", "repo", "title", "body", "head", "base"},
},
}
)
var BranchTools = []ToolDescription{
CreateBranchTool,
ListPullRequestsTool,
CreatePullRequestTool,
}
type RefObjectSchema struct {
Sha string `json:"sha"`
Type string `json:"type"`
URL string `json:"url"`
}
type RefSchema struct {
Ref string `json:"ref"`
NodeID string `json:"node_id"`
URL string `json:"url"`
Object RefObjectSchema `json:"object"`
}
func branchCreate(apiKey, owner, repo, branch string, fromBranch *string) CallToolResult {
from := "main"
if fromBranch != nil {
from = *fromBranch
}
sha, err := branchGetSha(apiKey, owner, repo, from)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to get sha for branch %s: %s", from, err)),
}},
}
}
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/git/refs", owner, repo)
req := pdk.NewHTTPRequest(pdk.MethodPost, url)
req.SetHeader("Authorization", fmt.Sprintf("token %s", apiKey))
req.SetHeader("Content-Type", "application/json")
req.SetHeader("Accept", "application/vnd.github.v3+json")
req.SetHeader("User-Agent", "github-mcpx-servlet")
data := map[string]interface{}{
"ref": fmt.Sprintf("refs/heads/%s", branch),
"sha": sha,
}
res, err := json.Marshal(data)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to marshal branch data: %s", err)),
}},
}
}
req.SetBody([]byte(res))
resp := req.Send()
if resp.Status() != 201 {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to create branch: %d %s", resp.Status(), string(resp.Body()))),
}},
}
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}
}
type PullRequestSchema struct {
Title string `json:"title"`
Body string `json:"body"`
Head string `json:"head"`
Base string `json:"base"`
Draft bool `json:"draft"`
MaintainerCanModify bool `json:"maintainer_can_modify"`
}
func branchPullRequestSchemaFromArgs(args map[string]interface{}) PullRequestSchema {
prs := PullRequestSchema{
Title: args["title"].(string),
Body: args["body"].(string),
Head: args["head"].(string),
Base: args["base"].(string),
}
if draft, ok := args["draft"].(bool); ok {
prs.Draft = draft
}
if canModify, ok := args["maintainer_can_modify"].(bool); ok {
prs.MaintainerCanModify = canModify
}
return prs
}
func pullRequestList(apiKey string, owner, repo string, args map[string]interface{}) (CallToolResult, error) {
baseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/pulls", owner, repo)
params := make([]string, 0)
// Handle state parameter
if state, ok := args["state"].(string); ok && state != "" {
switch state {
case "open", "closed", "all":
params = append(params, fmt.Sprintf("state=%s", state))
}
} else {
params = append(params, "state=open") // Default value
}
// Handle head parameter (user:ref-name or organization:ref-name format)
if head, ok := args["head"].(string); ok && head != "" {
params = append(params, fmt.Sprintf("head=%s", head))
}
// Handle base parameter
if base, ok := args["base"].(string); ok && base != "" {
params = append(params, fmt.Sprintf("base=%s", base))
}
// Handle sort parameter
sort := "created" // Default value
if sortArg, ok := args["sort"].(string); ok && sortArg != "" {
switch sortArg {
case "created", "updated", "popularity", "long-running":
sort = sortArg
}
}
params = append(params, fmt.Sprintf("sort=%s", sort))
// Handle direction parameter
direction := "desc" // Default for created or unspecified sort
if sort != "created" {
direction = "asc" // Default for other sort types
}
if dirArg, ok := args["direction"].(string); ok {
switch dirArg {
case "asc", "desc":
direction = dirArg
}
}
params = append(params, fmt.Sprintf("direction=%s", direction))
// Handle pagination
perPage := 30 // Default value
if perPageArg, ok := args["per_page"].(float64); ok {
if perPageArg > 100 {
perPage = 100 // Max value
} else if perPageArg > 0 {
perPage = int(perPageArg)
}
}
params = append(params, fmt.Sprintf("per_page=%d", perPage))
page := 1 // Default value
if pageArg, ok := args["page"].(float64); ok && pageArg > 0 {
page = int(pageArg)
}
params = append(params, fmt.Sprintf("page=%d", page))
// Build final URL
url := fmt.Sprintf("%s?%s", baseURL, strings.Join(params, "&"))
pdk.Log(pdk.LogDebug, fmt.Sprint("Listing pull requests: ", url))
// Make request
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
// Handle Accept header based on requested format
acceptHeader := "application/vnd.github+json" // Default recommended header
if format, ok := args["accept"].(string); ok {
switch format {
case "raw":
acceptHeader = "application/vnd.github.raw+json"
case "text":
acceptHeader = "application/vnd.github.text+json"
case "html":
acceptHeader = "application/vnd.github.html+json"
case "full":
acceptHeader = "application/vnd.github.full+json"
}
}
req.SetHeader("Accept", acceptHeader)
req.SetHeader("User-Agent", "github-mcpx-servlet")
resp := req.Send()
// Handle response status codes
switch resp.Status() {
case 200:
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}, nil
case 304:
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some("Not modified"),
}},
}, nil
case 422:
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some("Validation failed, or the endpoint has been spammed."),
}},
}, nil
default:
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Request failed with status %d: %s", resp.Status(), string(resp.Body()))),
}},
}, nil
}
}
func branchCreatePullRequest(apiKey, owner, repo string, pr PullRequestSchema) CallToolResult {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/pulls", owner, repo)
req := pdk.NewHTTPRequest(pdk.MethodPost, url)
req.SetHeader("Authorization", fmt.Sprintf("token %s", 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(pr)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to marshal pull request data: %s", err)),
}},
}
}
req.SetBody([]byte(res))
resp := req.Send()
if resp.Status() != 201 {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to create pull request: %d %s", resp.Status(), string(resp.Body()))),
}},
}
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}
}
func branchGetSha(apiKey, owner, repo, ref string) (string, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/git/refs/heads/%s", owner, repo, ref)
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
req.SetHeader("Authorization", fmt.Sprintf("token %s", apiKey))
req.SetHeader("Accept", "application/vnd.github.v3+json")
req.SetHeader("User-Agent", "github-mcpx-servlet")
resp := req.Send()
if resp.Status() != 200 {
return "", fmt.Errorf("Failed to get main branch sha: %d", resp.Status())
}
var refDetail RefSchema
json.Unmarshal(resp.Body(), &refDetail)
return refDetail.Object.Sha, nil
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/github/issues.go:
--------------------------------------------------------------------------------
```go
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/extism/go-pdk"
)
var (
ListIssuesTool = ToolDescription{
Name: "gh-list-issues",
Description: "List issues from a GitHub repository",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"filter": prop("string", "Filter by assigned, created, mentioned, subscribed, repos, all"),
"state": prop("string", "The state of the issues (open, closed, all)"),
"labels": prop("string", "A list of comma separated label names (e.g. bug,ui,@high)"),
"sort": prop("string", "Sort field (created, updated, comments)"),
"direction": prop("string", "Sort direction (asc or desc)"),
"since": prop("string", "ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SSZ)"),
"collab": prop("boolean", "Filter by issues that are collaborated on"),
"orgs": prop("boolean", "Filter by organization issues"),
"owned": prop("boolean", "Filter by owned issues"),
"pulls": prop("boolean", "Include pull requests in results"),
"per_page": prop("integer", "Number of results per page (max 100)"),
"page": prop("integer", "Page number for pagination"),
},
"required": []string{"owner", "repo"},
},
}
CreateIssueTool = ToolDescription{
Name: "gh-create-issue",
Description: "Create an issue on a GitHub repository",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"title": prop("string", "The title of the issue"),
"body": prop("string", "The body of the issue"),
"state": prop("string", "The state of the issue"),
"assignees": arrprop("array", "The assignees of the issue", "string"),
"milestone": prop("integer", "The milestone of the issue"),
},
"required": []string{"owner", "repo", "title", "body"},
},
}
GetIssueTool = ToolDescription{
Name: "gh-get-issue",
Description: "Get an issue from a GitHub repository",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"issue": prop("integer", "The issue number"),
},
"required": []string{"owner", "repo", "issue"},
},
}
AddIssueCommentTool = ToolDescription{
Name: "gh-add-issue-comment",
Description: "Add a comment to an issue in a GitHub repository",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"issue": prop("integer", "The issue number"),
"body": prop("string", "The body of the issue"),
},
"required": []string{"owner", "repo", "issue", "body"},
},
}
UpdateIssueTool = ToolDescription{
Name: "gh-update-issue",
Description: "Update an issue in a GitHub repository",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"issue": prop("integer", "The issue number"),
"title": prop("string", "The title of the issue"),
"body": prop("string", "The body of the issue"),
"state": prop("string", "The state of the issue"),
"assignees": arrprop("array", "The assignees of the issue", "string"),
"milestone": prop("integer", "The milestone of the issue"),
},
"required": []string{"owner", "repo", "issue"},
},
}
IssueTools = []ToolDescription{
ListIssuesTool,
CreateIssueTool,
GetIssueTool,
UpdateIssueTool,
AddIssueCommentTool,
}
)
type Issue struct {
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
Assignees []string `json:"assignees,omitempty"`
Milestone int `json:"milestone,omitempty"`
Labels []string `json:"labels,omitempty"`
}
func issueList(apiKey string, owner, repo string, args map[string]interface{}) (CallToolResult, error) {
baseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/issues", owner, repo)
params := make([]string, 0)
// String parameters
stringParams := map[string]string{
"filter": "assigned", // Default value
"state": "open", // Default value
"labels": "",
"sort": "created", // Default value
"direction": "desc", // Default value
"since": "",
}
for key := range stringParams {
if value, ok := args[key].(string); ok && value != "" {
params = append(params, fmt.Sprintf("%s=%s", key, value))
} else if stringParams[key] != "" {
// Add default value if one exists
params = append(params, fmt.Sprintf("%s=%s", key, stringParams[key]))
}
}
// Boolean parameters
boolParams := []string{"collab", "orgs", "owned", "pulls"}
for _, param := range boolParams {
if value, ok := args[param].(bool); ok {
params = append(params, fmt.Sprintf("%s=%t", param, value))
}
}
// Pagination parameters
perPage := 30 // Default value
if value, ok := args["per_page"].(float64); ok {
if value > 100 {
perPage = 100 // Max value
} else if value > 0 {
perPage = int(value)
}
}
params = append(params, fmt.Sprintf("per_page=%d", perPage))
page := 1 // Default value
if value, ok := args["page"].(float64); ok && value > 0 {
page = int(value)
}
params = append(params, fmt.Sprintf("page=%d", page))
// Build final URL
url := baseURL
if len(params) > 0 {
url = fmt.Sprintf("%s?%s", baseURL, strings.Join(params, "&"))
}
pdk.Log(pdk.LogDebug, fmt.Sprint("Listing issues: ", url))
// Make request
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
req.SetHeader("Accept", "application/vnd.github+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.Sprintf("Failed to list issues: %d %s", resp.Status(), string(resp.Body()))),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}, nil
}
func issueFromArgs(args map[string]interface{}) Issue {
data := Issue{}
if title, ok := args["title"].(string); ok {
data.Title = title
}
if body, ok := args["body"].(string); ok {
data.Body = body
}
if assignees, ok := args["assignees"].([]interface{}); ok {
for _, a := range assignees {
data.Assignees = append(data.Assignees, a.(string))
}
}
if milestone, ok := args["milestone"].(float64); ok {
data.Milestone = int(milestone)
}
if labels, ok := args["labels"].([]interface{}); ok {
for _, l := range labels {
data.Labels = append(data.Labels, l.(string))
}
}
return data
}
func issueCreate(apiKey string, owner, repo string, data Issue) (CallToolResult, error) {
url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/issues")
pdk.Log(pdk.LogDebug, fmt.Sprint("Adding comment: ", url))
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(data)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprint("Failed to create issue: ", 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 issue: ", resp.Status(), " ", string(resp.Body()))),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}, nil
}
func issueGet(apiKey string, owner, repo string, issue int) (CallToolResult, error) {
url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/issues/", issue)
pdk.Log(pdk.LogDebug, fmt.Sprint("Getting issue: ", url))
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 issue: ", resp.Status())),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}, nil
}
func issueUpdate(apiKey string, owner, repo string, issue int, data Issue) (CallToolResult, error) {
url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/issues/", issue)
pdk.Log(pdk.LogDebug, fmt.Sprint("Getting issue: ", url))
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, err := json.Marshal(data)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprint("Failed to update issue: ", err)),
}},
}, nil
}
req.SetBody([]byte(res))
resp := req.Send()
if resp.Status() != 200 {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprint("Failed to update issue: ", resp.Status())),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}, nil
}
func issueAddComment(apiKey string, owner, repo string, issue int, comment string) (CallToolResult, error) {
url := fmt.Sprint("https://api.github.com/repos/", owner, "/", repo, "/issues/", issue, "/comments")
pdk.Log(pdk.LogDebug, fmt.Sprint("Adding comment: ", url))
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(map[string]string{
"body": comment,
})
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprint("Failed to create issue: ", 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 add comment: ", resp.Status())),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}, nil
}
```
--------------------------------------------------------------------------------
/SKIP_TOOLS_GUIDE.md:
--------------------------------------------------------------------------------
```markdown
# Skip Tools Pattern Guide
This guide provides comprehensive documentation for using the `skip_tools` configuration in hyper-mcp, which allows you to filter out unwanted tools using powerful regex patterns.
## Overview
The `skip_tools` field in your plugin's `runtime_config` allows you to specify a list of regex patterns that will be used to exclude tools from being loaded at runtime. This is useful for:
- Removing debug tools in production environments
- Filtering out deprecated or experimental tools
- Excluding tools that conflict with your workflow
- Customizing the available tool set per environment
## How It Works
### Automatic Pattern Anchoring
All patterns in `skip_tools` are automatically anchored to match the entire tool name. This means:
```yaml
skip_tools:
- "debug" # Becomes "^debug$" - matches exactly "debug"
```
This prevents unintended partial matches. If you want to match parts of tool names, use explicit wildcards:
```yaml
skip_tools:
- "debug.*" # Matches "debug", "debugger", "debug_info", etc.
```
### Regex Compilation
All patterns are compiled into a single optimized `RegexSet` for efficient matching:
- O(1) lookup time regardless of pattern count
- Single compilation at startup
- Memory-efficient pattern storage
## Basic Patterns
### Exact Matches
Match specific tool names exactly:
```yaml
skip_tools:
- "debug_tool" # Matches only "debug_tool"
- "test_runner" # Matches only "test_runner"
- "admin_panel" # Matches only "admin_panel"
```
### Prefix Matching
Match tools that start with a specific string:
```yaml
skip_tools:
- "debug.*" # Matches "debug", "debugger", "debug_info"
- "test_.*" # Matches "test_unit", "test_integration", "test_e2e"
- "dev_.*" # Matches "dev_server", "dev_tools", "dev_helper"
```
### Suffix Matching
Match tools that end with a specific string:
```yaml
skip_tools:
- ".*_test" # Matches "unit_test", "integration_test", "load_test"
- ".*_backup" # Matches "data_backup", "config_backup", "db_backup"
- ".*_deprecated" # Matches "old_deprecated", "legacy_deprecated"
```
### Contains Matching
Match tools that contain a specific substring:
```yaml
skip_tools:
- ".*debug.*" # Matches "pre_debug_tool", "debug", "tool_debug_info"
- ".*temp.*" # Matches "temp_file", "cleanup_temp", "temp_storage_tool"
```
## Advanced Patterns
### Character Classes
Use character classes for flexible matching:
```yaml
skip_tools:
- "tool_[0-9]+" # Matches "tool_1", "tool_42", "tool_999"
- "test_[a-z]+" # Matches "test_unit", "test_api", "test_db"
- "[A-Z][a-z]+Tool" # Matches "DebugTool", "TestTool", "AdminTool"
- "log_[0-9]{4}_[0-9]{2}" # Matches "log_2023_12", "log_2024_01"
```
### Alternation (OR Logic)
Match multiple alternatives:
```yaml
skip_tools:
- "test_(unit|integration|e2e)" # Matches "test_unit", "test_integration", "test_e2e"
- "(debug|trace|log)_.*" # Matches tools starting with "debug_", "trace_", or "log_"
- ".*(temp|tmp|cache).*" # Matches tools containing "temp", "tmp", or "cache"
- "system_(admin|user|guest)_.*" # Matches tools for different user types
```
### Quantifiers
Control how many characters or groups to match:
```yaml
skip_tools:
- "tool_v[0-9]+" # Matches "tool_v1", "tool_v10", "tool_v123"
- "backup_[0-9]{8}" # Matches exactly 8 digits: "backup_20240101"
- "temp_[a-f0-9]{6,}" # Matches 6+ hex chars: "temp_abc123", "temp_def456789"
- "log_[0-9]{4}-[0-9]{2}" # Matches "log_2024-01", "log_2023-12"
```
### Negation with Character Classes
Skip tools that DON'T match certain patterns:
```yaml
skip_tools:
- "[^a-z].*" # Skip tools starting with non-lowercase letters
- ".*[^0-9]$" # Skip tools not ending with numbers
- "tool_[^v].*" # Skip tools starting with "tool_" but not "tool_v"
```
## Common Use Cases
### Environment-Specific Filtering
#### Development Environment
```yaml
skip_tools:
- "prod_.*" # Skip production tools
- "deploy_.*" # Skip deployment tools
- "monitor_.*" # Skip monitoring tools
```
#### Production Environment
```yaml
skip_tools:
- "debug.*" # Skip all debug tools
- "test_.*" # Skip all test tools
- "dev_.*" # Skip development tools
- "mock_.*" # Skip mock/stub tools
- ".*_experimental" # Skip experimental features
```
#### Testing Environment
```yaml
skip_tools:
- "prod_.*" # Skip production tools
- "deploy_.*" # Skip deployment tools
- ".*_live" # Skip live/production tools
```
### Tool Category Filtering
#### Skip Administrative Tools
```yaml
skip_tools:
- "admin_.*"
- "system_admin_.*"
- "user_management_.*"
- "permission_.*"
```
#### Skip Deprecated Tools
```yaml
skip_tools:
- ".*_deprecated"
- ".*_old"
- "legacy_.*"
- "v[0-9]_.*" # Skip versioned legacy tools
```
#### Skip Resource-Heavy Tools
```yaml
skip_tools:
- ".*_benchmark"
- "load_test_.*"
- "stress_.*"
- "heavy_.*"
```
### Version-Based Filtering
```yaml
skip_tools:
- ".*_v[0-9]" # Skip v1, v2, etc. (keep latest)
- ".*_beta" # Skip beta tools
- ".*_alpha" # Skip alpha tools
- "tool_[0-9]+\\.[0-9]+" # Skip versioned tools like "tool_1.0"
```
## Special Character Escaping
When matching literal special characters, escape them with backslashes:
```yaml
skip_tools:
- "file\\.exe" # Matches "file.exe" literally
- "script\\?" # Matches "script?" literally
- "temp\\*data" # Matches "temp*data" literally
- "path\\\\tool" # Matches "path\tool" literally (double escape for backslash)
- "price\\$calculator" # Matches "price$calculator" literally
- "regex\\[test\\]" # Matches "regex[test]" literally
```
## Configuration Examples
### Simple Configuration
```yaml
plugins:
my_plugin:
url: "oci://registry.io/my-plugin:latest"
runtime_config:
skip_tools:
- "debug_tool"
- "test_runner"
```
### Comprehensive Configuration
```yaml
plugins:
production_plugin:
url: "oci://registry.io/prod-plugin:latest"
runtime_config:
skip_tools:
# Exact matches
- "debug_console"
- "test_runner"
# Pattern matches
- "dev_.*" # All dev tools
- ".*_test" # All test tools
- "temp_.*" # All temp tools
- "mock_.*" # All mock tools
# Advanced patterns
- "tool_v[0-9]" # Versioned tools
- "admin_(user|role)_.*" # Specific admin tools
- "[0-9]+_backup" # Numbered backups
allowed_hosts: ["api.example.com"]
memory_limit: "512Mi"
```
### Multi-Environment Setup
```yaml
# config.dev.yaml
plugins:
app_plugin:
url: "oci://registry.io/app-plugin:dev"
runtime_config:
skip_tools:
- "prod_.*"
- "deploy_.*"
---
# config.prod.yaml
plugins:
app_plugin:
url: "oci://registry.io/app-plugin:latest"
runtime_config:
skip_tools:
- "debug.*"
- "test_.*"
- "dev_.*"
- ".*_experimental"
```
## Best Practices
### 1. Start Simple, Then Refine
```yaml
# Start with broad patterns
skip_tools:
- "debug.*"
- "test_.*"
# Refine to be more specific as needed
skip_tools:
- "debug_(console|panel)" # Only skip specific debug tools
- "test_(unit|integration)" # Only skip specific test types
```
### 2. Use Comments for Complex Patterns
```yaml
skip_tools:
- "tool_[0-9]+" # Skip numbered tools (tool_1, tool_2, etc.)
- ".*_(alpha|beta|rc[0-9]+)" # Skip pre-release versions
- "temp_[0-9]{8}_.*" # Skip dated temporary tools
```
### 3. Group Related Patterns
```yaml
skip_tools:
# Debug and development tools
- "debug.*"
- "dev_.*"
- ".*_dev"
# Testing tools
- "test_.*"
- ".*_test"
- "mock_.*"
# Administrative tools
- "admin_.*"
- "system_.*"
```
### 4. Consider Performance
```yaml
# Good: Specific patterns
skip_tools:
- "debug_tool"
- "test_runner"
# Less optimal: Overly broad patterns that might match many tools
skip_tools:
- ".*" # This would skip everything - not useful
```
## Troubleshooting
### Pattern Not Working?
1. **Check anchoring**: Remember patterns are auto-anchored
```yaml
# This matches only "debug" exactly
- "debug"
# This matches "debug", "debugger", "debug_tool", etc.
- "debug.*"
```
2. **Escape special characters**:
```yaml
# Wrong: Will treat . as wildcard
- "file.exe"
# Correct: Escapes the literal dot
- "file\\.exe"
```
3. **Test your patterns**: Use a regex tester to validate complex patterns
### Debugging Skip Rules
Enable debug logging to see which tools are being skipped:
```bash
RUST_LOG=debug hyper-mcp --config config.yaml
```
## Migration from Old Format
If you were using simple string arrays before:
```yaml
# Old format (if it existed)
skip_tools: ["debug_tool", "test_runner"]
# New format (same result, but now with regex support)
skip_tools: ["debug_tool", "test_runner"]
# New format with patterns (more powerful)
skip_tools: ["debug.*", "test_.*"]
```
## Error Handling
### Invalid Regex Patterns
If you provide an invalid regex pattern, configuration loading will fail:
```yaml
# This will cause an error - unclosed bracket
skip_tools:
- "tool_[invalid"
```
Error message will indicate the problematic pattern and suggest corrections.
### Empty Patterns
These configurations are all valid:
```yaml
# No skip_tools field - no tools skipped
runtime_config:
allowed_hosts: ["*"]
# Empty array - no tools skipped
runtime_config:
skip_tools: []
# Null value - no tools skipped
runtime_config:
skip_tools: null
```
## Performance Characteristics
- **Startup**: O(n) pattern compilation where n = number of patterns
- **Runtime**: O(1) tool name checking regardless of pattern count
- **Memory**: Minimal overhead, patterns compiled into efficient state machine
- **Scalability**: Handles hundreds of patterns efficiently
## Advanced Topics
### Complex Business Logic
```yaml
skip_tools:
# Skip tools for specific environments
- "prod_(?!api_).*" # Skip prod tools except prod_api_*
- "test_(?!smoke_).*" # Skip test tools except smoke tests
# Skip based on naming conventions
- "[A-Z]{2,}_.*" # Skip tools starting with 2+ capitals
- ".*_[0-9]{4}[0-9]{2}[0-9]{2}" # Skip daily-dated tools
```
### Integration with External Tools
You can generate `skip_tools` patterns dynamically:
```bash
# Generate patterns from external source
echo "skip_tools:" > config.yaml
external-tool --list-deprecated | sed 's/^/ - "/' | sed 's/$/\"/' >> config.yaml
```
### Conditional Configuration
Use different configs for different scenarios:
```yaml
# Base configuration
base_skip_patterns: &base_skip
- "debug.*"
- "test_.*"
# Environment-specific additions
prod_additional: &prod_additional
- "dev_.*"
- ".*_experimental"
plugins:
my_plugin:
runtime_config:
skip_tools:
- *base_skip
- *prod_additional # YAML doesn't support this directly,
# but you can use templating tools
```
This guide should help you make full use of the powerful `skip_tools` pattern matching capabilities in hyper-mcp!
```
--------------------------------------------------------------------------------
/examples/plugins/v1/meme-generator/src/lib.rs:
--------------------------------------------------------------------------------
```rust
mod embedded;
mod pdk;
use ab_glyph::{Font, FontArc, PxScale, ScaleFont};
use base64::Engine;
use extism_pdk::*;
use image::Rgba;
use imageproc::drawing::draw_text_mut;
use pdk::types::{
CallToolRequest, CallToolResult, Content, ContentType, ListToolsResult, ToolDescription,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::io::Cursor;
#[derive(Debug, Serialize, Deserialize)]
struct Example {
text: Vec<String>,
url: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct MemeTemplate {
id: String,
name: String,
lines: u32,
overlays: u32,
styles: Vec<String>,
blank: String,
example: Example,
source: Option<String>,
keywords: Vec<String>,
#[serde(rename = "_self")]
self_link: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct TemplateConfig {
name: String,
source: String,
keywords: Vec<String>,
text: Vec<TextConfig>,
example: Vec<String>,
overlay: Vec<OverlayConfig>,
}
#[derive(Debug, Serialize, Deserialize)]
struct TextConfig {
style: String,
color: String,
font: String,
anchor_x: f32,
anchor_y: f32,
angle: f32,
scale_x: f32,
scale_y: f32,
align: String,
start: f32,
stop: f32,
}
#[derive(Debug, Serialize, Deserialize)]
struct OverlayConfig {
center_x: f32,
center_y: f32,
angle: f32,
scale: f32,
}
pub(crate) fn call(input: CallToolRequest) -> Result<CallToolResult, Error> {
match input.params.name.as_str() {
"meme_list_templates" => list_templates(input),
"meme_get_template" => get_template(input),
"meme_generate" => generate_meme(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 list_templates(_input: CallToolRequest) -> Result<CallToolResult, Error> {
let templates_json = embedded::TEMPLATES_JSON;
let templates: Vec<MemeTemplate> = serde_json::from_str(templates_json)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(serde_json::to_string_pretty(&templates)?),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
fn get_template(input: CallToolRequest) -> Result<CallToolResult, Error> {
let args = input.params.arguments.unwrap_or_default();
let template_id = args
.get("template_id")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::msg("template_id is required"))?;
let templates: Vec<MemeTemplate> = serde_json::from_str(embedded::TEMPLATES_JSON)?;
let template = templates
.iter()
.find(|t| t.id == template_id)
.ok_or_else(|| Error::msg("Template not found"))?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: Some(serde_json::to_string_pretty(&template)?),
mime_type: Some("application/json".to_string()),
r#type: ContentType::Text,
data: None,
}],
})
}
fn generate_meme(input: CallToolRequest) -> Result<CallToolResult, Error> {
let args = input.params.arguments.unwrap_or_default();
let template_id = args
.get("template_id")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::msg("template_id is required"))?;
let texts = args
.get("texts")
.and_then(|v| v.as_array())
.ok_or_else(|| Error::msg("texts array is required"))?;
// Load template configuration
let config = TemplateConfig::load(template_id)?;
// Get the default image from embedded resources
let image_name = if embedded::get_template_image(template_id, "default.jpg").is_some() {
"default.jpg"
} else if embedded::get_template_image(template_id, "default.png").is_some() {
"default.png"
} else {
return Err(Error::msg(format!(
"No default template image found for {}",
template_id
)));
};
let image_data = embedded::get_template_image(template_id, image_name).ok_or_else(|| {
Error::msg(format!(
"Template image {} {} not found",
template_id, image_name
))
})?;
let mut image = image::load_from_memory(image_data)?.to_rgba8();
let (image_width, image_height) = image.dimensions();
let font = FontArc::try_from_slice(embedded::FONT_DATA)?;
// Draw each text configuration
for (i, text_config) in config.text.iter().enumerate() {
if i >= texts.len() {
break;
}
let text = texts[i]
.as_str()
.ok_or_else(|| Error::msg("Invalid text entry"))?;
let text = if text_config.style == "upper" {
text.to_uppercase()
} else {
text.to_string()
};
// Calculate initial desired height based on image dimensions and config scale
let desired_height = (image_height as f32 * text_config.scale_y).max(1.0);
// Calculate maximum available width based on alignment
let padding = image_width as f32 * 0.05; // 5% padding on each side
let available_width = match text_config.align.as_str() {
"center" => image_width as f32 - (2.0 * padding),
"left" => {
image_width as f32 - (image_width as f32 * text_config.anchor_x) - (2.0 * padding)
}
"right" => (image_width as f32 * text_config.anchor_x) - (2.0 * padding),
_ => image_width as f32 - (2.0 * padding),
};
// Calculate appropriate scale that prevents overflow
let scale = calculate_max_scale(&font, &text, available_width, desired_height);
// Calculate text width for positioning using the adjusted scale
let text_width = calculate_text_width(&font, &text, scale);
// Calculate x position based on anchor and alignment, now with padding
let x = match text_config.align.as_str() {
"center" => ((image_width as f32 - text_width) / 2.0
+ (image_width as f32 * text_config.anchor_x))
.max(padding) as i32,
"left" => ((image_width as f32 * text_config.anchor_x) + padding) as i32,
"right" => ((image_width as f32 * text_config.anchor_x) - text_width - padding)
.max(padding) as i32,
_ => ((image_width as f32 - text_width) / 2.0).max(padding) as i32,
};
// Calculate y position based on anchor
let y = (image_height as f32 * text_config.anchor_y) as i32;
// Convert color string to RGBA
let color = color_to_rgba(&text_config.color);
draw_text_mut(&mut image, color, x, y, scale, &font, &text);
}
// Convert image to bytes
let mut output_bytes = Vec::new();
let dynamic_image = image::DynamicImage::ImageRgba8(image);
dynamic_image.write_to(&mut Cursor::new(&mut output_bytes), image::ImageFormat::Png)?;
Ok(CallToolResult {
is_error: None,
content: vec![Content {
annotations: None,
text: None,
mime_type: Some("image/png".to_string()),
r#type: ContentType::Image,
data: Some(base64::engine::general_purpose::STANDARD.encode(&output_bytes)),
}],
})
}
fn calculate_text_width(font: &FontArc, text: &str, scale: PxScale) -> f32 {
let scaled_font = font.as_scaled(scale);
let mut width = 0.0;
for c in text.chars() {
let id = scaled_font.glyph_id(c);
width += scaled_font.h_advance(id);
if let Some(next_char) = text.chars().nth(1) {
let next_id = scaled_font.glyph_id(next_char);
width += scaled_font.kern(id, next_id);
}
}
width
}
fn color_to_rgba(color: &str) -> Rgba<u8> {
match color.to_lowercase().as_str() {
"white" => Rgba([255, 255, 255, 255]),
"black" => Rgba([0, 0, 0, 255]),
"red" => Rgba([255, 0, 0, 255]),
"green" => Rgba([0, 255, 0, 255]),
"blue" => Rgba([0, 0, 255, 255]),
_ => Rgba([255, 255, 255, 255]), // Fallback to white
}
}
fn calculate_max_scale(
font: &FontArc,
text: &str,
target_width: f32,
desired_height: f32,
) -> PxScale {
let initial_scale = PxScale::from(desired_height);
let initial_width = calculate_text_width(font, text, initial_scale);
if initial_width <= target_width {
return initial_scale;
}
// Scale down proportionally if text is too wide
let scale_factor = target_width / initial_width;
PxScale::from(desired_height * scale_factor)
}
impl TemplateConfig {
fn load(template_id: &str) -> Result<Self, Error> {
let config_contents = embedded::get_template_config(template_id)
.ok_or_else(|| Error::msg(format!("Template {} not found", template_id)))?;
let config: TemplateConfig = serde_yaml::from_str(config_contents)
.map_err(|e| Error::msg(format!("Failed to parse config: {}", e)))?;
Ok(config)
}
}
pub(crate) fn describe() -> Result<ListToolsResult, Error> {
Ok(ListToolsResult {
tools: vec![
ToolDescription {
name: "meme_list_templates".into(),
description: "Lists all available meme templates".into(),
input_schema: json!({
"type": "object",
"properties": {},
"required": []
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "meme_get_template".into(),
description: "Get details about a specific meme template".into(),
input_schema: json!({
"type": "object",
"properties": {
"template_id": {
"type": "string",
"description": "The ID of the template to retrieve",
}
},
"required": ["template_id"]
})
.as_object()
.unwrap()
.clone(),
},
ToolDescription {
name: "meme_generate".into(),
description: "Generate a meme using a template and custom text".into(),
input_schema: json!({
"type": "object",
"properties": {
"template_id": {
"type": "string",
"description": "The ID of the template to use",
},
"texts": {
"type": "array",
"items": {
"type": "string"
},
"description": "Array of text strings to place on the meme",
}
},
"required": ["template_id", "texts"]
})
.as_object()
.unwrap()
.clone(),
},
],
})
}
```
--------------------------------------------------------------------------------
/examples/plugins/v1/github/repo.go:
--------------------------------------------------------------------------------
```go
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/extism/go-pdk"
)
var (
GetRepositoryContributorsTool = ToolDescription{
Name: "gh-get-repo-contributors",
Description: "Get the list of contributors for a GitHub repository, including their contributions count and profile details",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"per_page": prop("integer", "Number of results per page (max 100)"),
"page": prop("integer", "Page number for pagination"),
},
"required": []string{"owner", "repo"},
},
}
GetRepositoryCollaboratorsTool = ToolDescription{
Name: "gh-get-repo-collaborators",
Description: "Get the list of collaborators for a GitHub repository, including their permissions and profile details",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
"per_page": prop("integer", "Number of results per page (max 100)"),
"page": prop("integer", "Page number for pagination"),
},
"required": []string{"owner", "repo"},
},
}
GetRepositoryDetailsTool = ToolDescription{
Name: "gh-get-repo-details",
Description: "Get detailed information about a GitHub repository, including stars, forks, issues, and more",
InputSchema: schema{
"type": "object",
"properties": props{
"owner": prop("string", "The owner of the repository"),
"repo": prop("string", "The repository name"),
},
"required": []string{"owner", "repo"},
},
}
ListReposTool = ToolDescription{
Name: "gh-list-repos",
Description: "List repositories for a GitHub user or organization",
InputSchema: schema{
"type": "object",
"properties": props{
"username": prop("string", "The GitHub username or organization name"),
"type": prop("string", "The type of repositories to list (all, owner, member)"),
"sort": prop("string", "The sort field (created, updated, pushed, full_name)"),
"direction": prop("string", "The sort direction (asc or desc)"),
"per_page": prop("integer", "Number of results per page (max 100)"),
"page": prop("integer", "Page number for pagination"),
},
"required": []string{"username"},
},
}
RepoTools = []ToolDescription{
GetRepositoryContributorsTool,
GetRepositoryCollaboratorsTool,
GetRepositoryDetailsTool,
ListReposTool,
}
)
type Contributor struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
Contributions int `json:"contributions"`
}
func reposGetContributors(apiKey string, owner, repo string, args map[string]interface{}) (CallToolResult, error) {
baseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/contributors", owner, repo)
params := make([]string, 0)
// Pagination parameters
perPage := 30 // Default value
if value, ok := args["per_page"].(float64); ok {
if value > 100 {
perPage = 100 // Max value
} else if value > 0 {
perPage = int(value)
}
}
params = append(params, fmt.Sprintf("per_page=%d", perPage))
page := 1 // Default value
if value, ok := args["page"].(float64); ok && value > 0 {
page = int(value)
}
params = append(params, fmt.Sprintf("page=%d", page))
// Build final URL
url := baseURL
if len(params) > 0 {
url = fmt.Sprintf("%s?%s", baseURL, strings.Join(params, "&"))
}
pdk.Log(pdk.LogDebug, fmt.Sprint("Fetching contributors: ", url))
// Make request
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
req.SetHeader("Accept", "application/vnd.github+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.Sprintf("Failed to fetch contributors: %d %s", resp.Status(), string(resp.Body()))),
}},
}, nil
}
// Parse the response
var contributors []Contributor
if err := json.Unmarshal(resp.Body(), &contributors); err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to parse contributors: %s", err)),
}},
}, nil
}
// Marshal the response
responseJSON, err := json.Marshal(contributors)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to marshal response: %s", err)),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(responseJSON)),
}},
}, nil
}
type Collaborator struct {
Login string `json:"login"`
ID int `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
Permissions struct {
Admin bool `json:"admin"`
Push bool `json:"push"`
Pull bool `json:"pull"`
} `json:"permissions"`
}
func reposGetCollaborators(apiKey string, owner, repo string, args map[string]interface{}) (CallToolResult, error) {
baseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/collaborators", owner, repo)
params := make([]string, 0)
// Pagination parameters
perPage := 30 // Default value
if value, ok := args["per_page"].(float64); ok {
if value > 100 {
perPage = 100 // Max value
} else if value > 0 {
perPage = int(value)
}
}
params = append(params, fmt.Sprintf("per_page=%d", perPage))
page := 1 // Default value
if value, ok := args["page"].(float64); ok && value > 0 {
page = int(value)
}
params = append(params, fmt.Sprintf("page=%d", page))
// Build final URL
url := baseURL
if len(params) > 0 {
url = fmt.Sprintf("%s?%s", baseURL, strings.Join(params, "&"))
}
pdk.Log(pdk.LogDebug, fmt.Sprint("Fetching collaborators: ", url))
// Make request
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
req.SetHeader("Accept", "application/vnd.github+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.Sprintf("Failed to fetch collaborators: %d %s", resp.Status(), string(resp.Body()))),
}},
}, nil
}
// Parse the response
var collaborators []Collaborator
if err := json.Unmarshal(resp.Body(), &collaborators); err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to parse collaborators: %s", err)),
}},
}, nil
}
// Marshal the response
responseJSON, err := json.Marshal(collaborators)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to marshal response: %s", err)),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(responseJSON)),
}},
}, nil
}
type RepositoryDetails struct {
Name string `json:"name"`
FullName string `json:"full_name"`
Description string `json:"description"`
Private bool `json:"private"`
Owner struct {
Login string `json:"login"`
} `json:"owner"`
HTMLURL string `json:"html_url"`
Stargazers int `json:"stargazers_count"`
Watchers int `json:"watchers_count"`
Forks int `json:"forks_count"`
OpenIssues int `json:"open_issues_count"`
DefaultBranch string `json:"default_branch"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
PushedAt string `json:"pushed_at"`
}
func reposGetDetails(apiKey string, owner, repo string) (CallToolResult, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s", owner, repo)
pdk.Log(pdk.LogDebug, fmt.Sprint("Fetching repository details: ", url))
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
req.SetHeader("Accept", "application/vnd.github+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.Sprintf("Failed to fetch repository details: %d %s", resp.Status(), string(resp.Body()))),
}},
}, nil
}
var repoDetails RepositoryDetails
if err := json.Unmarshal(resp.Body(), &repoDetails); err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to parse repository details: %s", err)),
}},
}, nil
}
responseJSON, err := json.Marshal(repoDetails)
if err != nil {
return CallToolResult{
IsError: some(true),
Content: []Content{{
Type: ContentTypeText,
Text: some(fmt.Sprintf("Failed to marshal response: %s", err)),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(responseJSON)),
}},
}, nil
}
func reposList(apiKey string, username string, args map[string]interface{}) (CallToolResult, error) {
baseURL := fmt.Sprintf("https://api.github.com/users/%s/repos", username)
params := make([]string, 0)
// Optional parameters
if value, ok := args["type"].(string); ok && value != "" {
params = append(params, fmt.Sprintf("type=%s", value))
}
if value, ok := args["sort"].(string); ok && value != "" {
params = append(params, fmt.Sprintf("sort=%s", value))
}
if value, ok := args["direction"].(string); ok && value != "" {
params = append(params, fmt.Sprintf("direction=%s", value))
}
// Pagination parameters
perPage := 30 // Default value
if value, ok := args["per_page"].(float64); ok {
if value > 100 {
perPage = 100 // Max value
} else if value > 0 {
perPage = int(value)
}
}
params = append(params, fmt.Sprintf("per_page=%d", perPage))
page := 1 // Default value
if value, ok := args["page"].(float64); ok && value > 0 {
page = int(value)
}
params = append(params, fmt.Sprintf("page=%d", page))
// Build final URL
url := baseURL
if len(params) > 0 {
url = fmt.Sprintf("%s?%s", baseURL, strings.Join(params, "&"))
}
pdk.Log(pdk.LogDebug, fmt.Sprint("Fetching repositories: ", url))
// Make request
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
req.SetHeader("Authorization", fmt.Sprint("token ", apiKey))
req.SetHeader("Accept", "application/vnd.github+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.Sprintf("Failed to fetch repositories: %d %s", resp.Status(), string(resp.Body()))),
}},
}, nil
}
return CallToolResult{
Content: []Content{{
Type: ContentTypeText,
Text: some(string(resp.Body())),
}},
}, nil
}
```