Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ jobs:
- name: Verify the module loads and serves its interface
run: |
set -euo pipefail
cargo build --all-features --package tinymcp --lib
# A static-link build has Rust entries but no dynamic ABI symbols.
cargo build --package tinymcp --lib
Comment thread
senamakel marked this conversation as resolved.
cargo run --package tinymcp --example verify_module -- \
target/debug/libtinymcp.so

Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,16 @@ root `Cargo.toml`.
## License

GPL-3.0-only. See [LICENSE](LICENSE).

## Static linking

Enable the `static-link` feature when compiling this module into a Rust host. It
exposes `tinybus_module::TINYBUS_MODULE_ABI_V1`,
`tinybus_module::tinybus_module_manifest_v1`, and
`tinybus_module::tinybus_module_init_v1` as Rust-addressable entries for the
TinyBus linked-module loader. The default build retains the native loadable
module exports. Build without `static-link` when packaging a `cdylib`.
Since `--all-features` includes `static-link`, its `cdylib` has Rust-addressable
entries rather than exported C ABI symbols; run `verify_module` against a
default-feature build. The static entry test covers the all-features mode,
while `verify_module` parses and checks the generated manifest in dynamic mode.
10 changes: 10 additions & 0 deletions crates/tinymcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ sha2 = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

[[example]]
name = "verify_module"
required-features = ["module"]

[[example]]
name = "verify_github_release"
required-features = ["module"]

[dev-dependencies]
# `net` stands up the loopback test servers; `test-util` pauses the clock so a
# five-minute timeout is testable without waiting five minutes. The rest comes
Expand All @@ -57,6 +65,8 @@ default = ["module", "rustls-tls"]
# released artifact is a loadable module; a host consuming this crate as a path
# dependency turns it off and links neither `tinybus` nor its module SDK.
module = ["dep:tinybus", "dep:tinybus-module"]
# Link the TinyBus module into a host binary with Rust-addressable entry points.
static-link = ["module"]
# Bundle a TLS stack, so a build needs no system OpenSSL. The default.
rustls-tls = ["reqwest/rustls-tls"]
# Use the operating system's TLS stack and certificate store instead. A host
Expand Down
4 changes: 3 additions & 1 deletion crates/tinymcp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ pub mod audit;
pub mod config_servers;
mod error;
pub mod registry;
#[cfg(feature = "module")]
#[cfg(all(feature = "module", not(feature = "static-link")))]
mod tinybus_module;
#[cfg(feature = "static-link")]
pub mod tinybus_module;
pub mod transport;

pub use audit::AuditStore;
Expand Down
23 changes: 22 additions & 1 deletion crates/tinymcp/src/tinybus_module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,28 @@ pub(super) async fn setup(connection: Connection, config: ModuleConfig) -> TinyB
Ok(())
}

tinybus_module::module_export! {
macro_rules! export_module {
($($declaration:tt)*) => {
#[cfg(feature = "static-link")]
mod linked_exports {
// TinyBus generates these three ABI items without rustdoc.
#![expect(missing_docs, reason = "generated TinyBus ABI entries")]
use super::*;
tinybus_module::module_export_static! { $($declaration)* }
Comment thread
senamakel marked this conversation as resolved.
}
#[cfg(feature = "static-link")]
pub use linked_exports::{
TINYBUS_MODULE_ABI_V1, tinybus_module_init_v1, tinybus_module_manifest_v1,
};
#[cfg(not(feature = "static-link"))]
mod dynamic_exports {
use super::*;
tinybus_module::module_export! { $($declaration)* }
}
};
}

export_module! {
setup = setup,
config = ModuleConfig,
// More than one, because a tool call on one server must not wait behind a
Expand Down
13 changes: 9 additions & 4 deletions crates/tinymcp/src/tinybus_module/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,9 @@ impl McpService {

// Every member is `async fn` because the interface macro requires it: it
// rejects a blocking method outright, on the grounds that one would stall the
// connection's dispatch task for every other caller. A handful of members have
// nothing to await, and that is fine — the uniformity is what the macro is
// buying, and it is not this impl's call to make.
#[allow(clippy::unused_async_trait_impl)]
// connection's dispatch task for every other caller. Synchronous members await
// an immediately ready future so both supported Clippy versions accept the
// required async signature without changing scheduling.
#[tinybus::interface(name = "ai.tinyhumans.tinymcp.Mcp")]
impl McpService {
// -- browsing -----------------------------------------------------------
Expand Down Expand Up @@ -153,6 +152,7 @@ impl McpService {

/// `()`
async fn registry_settings_get(&self) -> tinybus::Result<RegistrySettings> {
std::future::ready(()).await;
Ok(self.dynamic.registry_settings())
}

Expand All @@ -166,6 +166,7 @@ impl McpService {
mcp_official_base: Option<String>,
mcp_official_token: Option<String>,
) -> tinybus::Result<RegistrySettings> {
std::future::ready(()).await;
Ok(self.dynamic.set_registry_settings(
smithery_api_key,
mcp_official_base,
Expand All @@ -177,6 +178,7 @@ impl McpService {

/// `()`
async fn installed_list(&self) -> tinybus::Result<Vec<InstalledServer>> {
std::future::ready(()).await;
self.dynamic
.installed_list()
.map_err(|error| Self::failed(&error))
Expand Down Expand Up @@ -396,6 +398,7 @@ impl McpService {

/// `()` — the names the host declared.
async fn static_list(&self) -> tinybus::Result<Vec<String>> {
std::future::ready(()).await;
Ok(self
.static_servers
.list()
Expand Down Expand Up @@ -438,6 +441,7 @@ impl McpService {

/// `(record)` — returns the row identifier.
async fn audit_record_write(&self, record: NewMcpWriteRecord) -> tinybus::Result<i64> {
std::future::ready(()).await;
self.audit
.record(&record)
.map_err(|error| Self::failed(&error))
Expand All @@ -448,6 +452,7 @@ impl McpService {
&self,
query: McpWriteListQuery,
) -> tinybus::Result<Vec<McpWriteRecord>> {
std::future::ready(()).await;
self.audit
.list(&query)
.map_err(|error| Self::failed(&error))
Expand Down
17 changes: 17 additions & 0 deletions crates/tinymcp/tests/static_link.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! Checks the Rust-addressable `TinyBus` entries in a statically linked build.

#![cfg(feature = "static-link")]

use tinymcp::tinybus_module::{
TINYBUS_MODULE_ABI_V1, tinybus_module_init_v1, tinybus_module_manifest_v1,
};

#[test]
fn linked_module_exposes_descriptor_manifest_and_init() {
let descriptor = &TINYBUS_MODULE_ABI_V1;
let mut expected_name = [0; 64];
expected_name[..7].copy_from_slice(b"tinymcp");
assert_eq!(descriptor.module_name, expected_name);
assert!(tinybus_module_manifest_v1().len > 0);
Comment thread
senamakel marked this conversation as resolved.
Comment thread
senamakel marked this conversation as resolved.
assert_ne!(tinybus_module_init_v1 as *const () as usize, 0);
}
Loading