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
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ internal refactors, CI, or test-only changes.

## [Unreleased]

## [1.0.1] - 2026-07-21

### Fixed
- **`glass-mcp` reports its real version.** `--version`, `glass-mcp doctor`, and the MCP
`initialize` handshake previously reported `0.0.0` (the crate is versioned by release tag, not in
`Cargo.toml`), and the handshake additionally identified the server by the transport library's
name and version rather than glass's. The version is now derived at build time from the release
tag, and the handshake identifies the server as `glass-mcp`.

## [1.0.0] - 2026-07-21

*First stable release. glass's agent-facing surface — tool names and parameters, result shapes, enum
Expand Down Expand Up @@ -322,7 +331,8 @@ First public release — open core, Apache-2.0.
- Core tools: `glass_start`, `glass_stop`, `glass_screenshot`, `glass_click`,
`glass_list_windows`, `glass_select_window`, and `glass_doctor`.

[Unreleased]: https://github.com/fixed-width/glass/compare/v1.0.0...HEAD
[Unreleased]: https://github.com/fixed-width/glass/compare/v1.0.1...HEAD
[1.0.1]: https://github.com/fixed-width/glass/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/fixed-width/glass/compare/v0.5.0...v1.0.0
[0.5.0]: https://github.com/fixed-width/glass/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/fixed-width/glass/compare/v0.3.1...v0.4.0
Expand Down
38 changes: 38 additions & 0 deletions crates/glass-mcp/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,43 @@ fn main() {
// off non-Windows builds entirely so build.rs always compiles there.
#[cfg(windows)]
static_vcruntime::metabuild();

// The user-facing version. The crate version is pinned at 0.0.0 (releases are tag-driven,
// not version-bumped), so derive the real version at build time: the release tag in CI, else
// the nearest git tag for a local build, else the crate version as a last resort. Consumed via
// `env!("GLASS_VERSION")` by `--version`, `doctor`, and the MCP handshake so none report 0.0.0.
println!("cargo:rustc-env=GLASS_VERSION={}", glass_version());
println!("cargo:rerun-if-env-changed=GITHUB_REF_NAME");
println!("cargo:rerun-if-env-changed=GITHUB_REF_TYPE");
println!("cargo:rerun-if-changed=build.rs");
}

/// Resolve the version string embedded at build time. Strips a leading `v` so `v1.0.1` → `1.0.1`.
fn glass_version() -> String {
// Release builds are a TAG push in CI, where `GITHUB_REF_TYPE=tag` and `GITHUB_REF_NAME` is the
// tag (e.g. `v1.0.1`). Gate on the ref TYPE: `GITHUB_REF_NAME` is also set on branch/PR builds
// (as the branch name), which must NOT become the version.
if std::env::var("GITHUB_REF_TYPE").as_deref() == Ok("tag") {
if let Ok(tag) = std::env::var("GITHUB_REF_NAME") {
let v = tag.strip_prefix('v').unwrap_or(&tag).trim();
if !v.is_empty() {
return v.to_string();
}
}
}
// Local builds: derive from the nearest tag (with a `-dirty` / commit suffix when not exactly
// on a tag), so a dev binary reports an honest version rather than 0.0.0.
if let Ok(out) = std::process::Command::new("git")
.args(["describe", "--tags", "--always", "--dirty"])
.output()
{
if out.status.success() {
let d = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !d.is_empty() {
return d.strip_prefix('v').unwrap_or(&d).to_string();
}
}
}
// No tag and no git (e.g. a source tarball with no VCS): fall back to the crate version.
env!("CARGO_PKG_VERSION").to_string()
}
14 changes: 13 additions & 1 deletion crates/glass-mcp/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(
name = "glass-mcp",
version,
version = crate::VERSION,
about = "glass MCP server — a build→see→interact→debug loop over native GUI apps",
after_help = "With no command, glass-mcp serves MCP over stdio (the default)."
)]
Expand Down Expand Up @@ -280,4 +280,16 @@ mod tests {
let dh = Cli::try_parse_from(["glass-mcp", "doctor", "--help"]).unwrap_err();
assert_eq!(dh.kind(), clap::error::ErrorKind::DisplayHelp);
}

#[test]
fn version_is_the_build_time_glass_version_not_the_crate_default() {
use clap::CommandFactory;
// `--version` must render the build-time `GLASS_VERSION` (via `crate::VERSION`), not the
// crate's pinned `CARGO_PKG_VERSION` (`0.0.0`).
assert_eq!(Cli::command().get_version(), Some(crate::VERSION));
assert!(
!crate::VERSION.is_empty(),
"GLASS_VERSION must be populated by build.rs"
);
}
}
19 changes: 18 additions & 1 deletion crates/glass-mcp/src/doctor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn diagnose_inner(deep: bool, audit: Option<&crate::audit::AuditReport>) -> Diag
vec![
default_backend_check(raw.as_deref()),
sandbox_floor_check(sandbox_floor_raw.as_deref(), sandbox_floor_bad_utf8),
Check::new("glass", CheckStatus::Ok, env!("CARGO_PKG_VERSION")),
Check::new("glass", CheckStatus::Ok, crate::VERSION),
],
);

Expand Down Expand Up @@ -643,6 +643,23 @@ mod tests {
);
}

#[test]
fn diagnose_general_section_reports_the_build_time_glass_version() {
// The "glass" check must show `crate::VERSION` (build-time), not the crate's `0.0.0`.
let d = diagnose(false);
let general = d
.sections
.iter()
.find(|s| s.title == "general")
.expect("general section");
let glass = general
.checks
.iter()
.find(|c| c.name == "glass")
.expect("glass version check");
assert_eq!(glass.detail, crate::VERSION);
}

#[test]
fn idb_companion_check_fails_when_absent_and_oks_when_present() {
// The iOS companion is *required* to drive apps (unlike android's optional companions);
Expand Down
4 changes: 4 additions & 0 deletions crates/glass-mcp/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ pub(crate) const INTERNAL_ENV: &[&str] = &[
// Forces a test-only AX-geometry fallback path (glass-macos/src/axwindow.rs); read only to
// exercise that path in an integration test, not a supported way to configure glass.
"GLASS_MACOS_FORCE_AX_GEOMETRY_FALLBACK",
// Build-time only: `build.rs` computes the release version and emits it as a
// `cargo:rustc-env=GLASS_VERSION`, read via `env!` (see `crate::VERSION`). Not read from the
// process environment and never an operator override.
"GLASS_VERSION",
];

/// Standard (non-`GLASS_*`) env glass reads at runtime — reference only.
Expand Down
5 changes: 5 additions & 0 deletions crates/glass-mcp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
//! glass-mcp library root. `main.rs` is a thin binary over this so the serve
//! path is reachable from integration tests.

/// The user-facing glass version, resolved at build time (see `build.rs`): the release tag in CI,
/// else the nearest git tag locally, else the crate version. Single source for `--version`,
/// `doctor`, and the MCP `initialize` handshake — the crate version itself is pinned at 0.0.0.
pub const VERSION: &str = env!("GLASS_VERSION");

pub mod audit;
pub mod capabilities;
pub mod cli;
Expand Down
32 changes: 30 additions & 2 deletions crates/glass-mcp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,16 @@ const SERVER_INSTRUCTIONS: &str =
#[tool_handler(router = self.tool_router)]
impl ServerHandler for GlassServer {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
.with_instructions(SERVER_INSTRUCTIONS)
let mut info = ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
.with_instructions(SERVER_INSTRUCTIONS);
// Identify the server as glass in the MCP `initialize` handshake. The rmcp default
// (`Implementation::from_build_env`) reports the transport crate's own name and version
// (`rmcp` / its crate version), not glass's — so every connecting client would see the wrong
// server identity. Override with glass's name + build-time version.
info.server_info.name = "glass-mcp".to_string();
info.server_info.title = Some("glass".to_string());
info.server_info.version = crate::VERSION.to_string();
info
}
}

Expand Down Expand Up @@ -648,6 +656,26 @@ mod tests {
assert!(first_text(&out).contains("nope"));
}

#[test]
fn get_info_identifies_the_server_as_glass_not_the_transport_crate() {
let glass =
crate::tools::testutil::glass_with(crate::tools::testutil::FakePlatform::new(10, 10));
let report = crate::audit::report_from_config(None, |_| None);
let server = GlassServer::new(glass, report);
let info = server.get_info();
// Must override the rmcp default (which reports the transport crate's own name/version).
assert_eq!(
info.server_info.name, "glass-mcp",
"the MCP handshake must identify glass, not the rmcp transport crate"
);
assert_eq!(info.server_info.title.as_deref(), Some("glass"));
assert_eq!(
info.server_info.version,
crate::VERSION,
"handshake version must be glass's build-time version, not the crate's 0.0.0 or rmcp's"
);
}

#[tokio::test]
async fn glass_doctor_envelopes_the_report_text() {
let glass =
Expand Down