Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Stand In Macros

FreeNot checked

Procedural macros for stand-in MCP server framework.

GitHubEmbed

About

Procedural macros for stand-in MCP server framework.

README

Build Crates.io Docs.rs License: MIT

stand-in logo

A stand-in for your MCP server boilerplate.

You write with stand-in declarative macros that look like your MCP server — tools, resources, prompts — but when the compiler rolls, the macros step aside and production-ready code takes their place. You never touch the generated code. You only ever interact with the stand-in.

Status

🚧 Early Development — Core macros (#[mcp_tool], #[mcp_server], #[mcp_prompt], #[mcp_resource]) and both transports (Stdio, Streamable HTTP) are implemented. APIs may change before 1.0.

Installation

Add to your Cargo.toml:

[dependencies]
stand-in = "0.0.4"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Feature Flags

Feature Default Description
stdio Stdio transport for local/CLI usage
http Streamable HTTP transport (MCP spec 2025-03-26)

To enable HTTP transport:

stand-in = { version = "0.0.3", features = ["http"] }

Quick Start

use stand_in::prelude::*;

#[mcp_tool(
    name = "get_weather",
    description = "Returns current weather for a given city"
)]
async fn get_weather(city: String) -> Result<String> {
    let forecast = fetch_weather(&city).await?;
    Ok(format!("{}: {}°C, {}", city, forecast.temp, forecast.condition))
}

#[mcp_server]
struct MyServer;

#[tokio::main]
async fn main() {
    MyServer::serve(StdioTransport::default()).await;
}

That's it. No handler registration. No JSON-RPC routing. No protocol plumbing. The stand-in handles the setup; the compiler delivers the performance.

Adding a Prompt

use stand_in::prelude::*;

#[mcp_prompt(
    name = "summarize",
    description = "Summarize a document for a given audience"
)]
async fn summarize(document: String, audience: Option<String>) -> Result<Prompt> {
    let level = audience.as_deref().unwrap_or("general");
    Ok(Prompt::user(format!(
        "Summarize the following for a {level} audience:\n\n{document}"
    )))
}

Option<T> parameters become optional arguments in the MCP prompt definition. Required parameters stay required. The return type is always Result<Prompt>.

Adding a Resource

use stand_in::prelude::*;

/// Concrete resource — fixed URI, no parameters.
#[mcp_resource(
    uri = "info://version",
    name = "Server Version",
    mime_type = "application/json"
)]
async fn server_version() -> Result<String> {
    Ok(serde_json::json!({"version": "1.0.0"}).to_string())
}

/// Template resource — URI template with {param}, extracted at read time.
#[mcp_resource(
    uri = "docs://{topic}/readme",
    name = "Documentation",
    description = "Documentation for a given topic",
    mime_type = "text/markdown"
)]
async fn docs_readme(topic: String) -> Result<String> {
    Ok(format!("# {topic}\n\nDocumentation for {topic}."))
}

Resources with {param} in the URI become template resources. Concrete resources (no {param}) appear in resources/list; templates appear in resources/templates/list. Return Result<Vec<u8>> for binary data — the macro auto-detects the return type and produces base64-encoded BlobResourceContents.

Philosophy

Inspired by frameworks like Spring Boot, stand-in follows a simple principle: convention eliminates configuration. If the shape of your code already tells us what you mean, you shouldn't have to say it twice.

The MCP protocol is well-defined but verbose to implement. Every server needs the same handshake, the same capability negotiation, the same JSON-RPC dispatch. stand-in absorbs all of that behind derive macros and attribute macros, so you focus on what your server does — not on how it speaks the protocol.

Workspace Structure

The project is organized as a Cargo workspace with two crates:

Crate Role
stand-in The main library. Re-exports macros, provides runtime, transport, and protocol types.
stand-in-macros Procedural macros. Generates the JSON-RPC dispatch, capability advertisement, and handler wiring at compile time.
stand-in/
├── Cargo.toml              # workspace root
├── stand-in/
│   ├── Cargo.toml
│   └── src/
│       ├── lib.rs           # re-exports, runtime, transports
│       └── ...
└── stand-in-macros/
    ├── Cargo.toml
    └── src/
        └── lib.rs           # proc macros: #[mcp_server], #[mcp_tool], etc.

Features

  • #[mcp_tool] — Declare a tool with typed parameters. Schema is inferred from the function signature.
  • #[mcp_prompt] — Define reusable prompt templates with typed arguments. Arguments are inferred from the function signature; Option<T> parameters are optional.
  • #[mcp_server] — Wire everything together. Generates initialization, capability negotiation, and dispatch.
  • Transports — Stdio (default) and Streamable HTTP (feature-gated). Extensible via the Transport trait.
  • Async-first — Built on tokio. Every handler is async fn.
  • #[mcp_resource] — Expose data as MCP resources (concrete URIs or URI templates with {param}). Return Result<String> for text content or Result<Vec<u8>> for base64-encoded blobs.

Example: A More Complete Server

use stand_in::prelude::*;

/// A server that exposes project management tools and prompts.
/// Server name and version are read from Cargo.toml at compile time.
#[mcp_server]
struct ProjectHub;

#[mcp_tool(name = "list_tasks", description = "List all open tasks for a project")]
async fn list_tasks(project_id: String) -> Result<String> {
    // ... fetch from database
    Ok(format!("Tasks for {project_id}: ..."))
}

#[mcp_tool(name = "create_task", description = "Create a new task")]
async fn create_task(project_id: String, title: String, assignee: Option<String>) -> Result<String> {
    // ... write to database
    Ok(format!("Created task '{title}' in {project_id}"))
}

#[mcp_prompt(
    name = "summarize_project",
    description = "Generate a project status summary"
)]
async fn summarize_project(project_id: String, format: Option<String>) -> Result<Prompt> {
    let level = format.as_deref().unwrap_or("brief");
    Ok(Prompt::user(format!(
        "Summarize project {project_id} in a {level} format."
    )))
}

#[mcp_resource(
    uri = "project://{project_id}/overview",
    name = "Project Overview",
    mime_type = "application/json"
)]
async fn project_overview(project_id: String) -> Result<String> {
    Ok(serde_json::json!({"id": project_id, "status": "active"}).to_string())
}

#[tokio::main]
async fn main() {
    ProjectHub::serve(StdioTransport::default()).await.unwrap();
}

stand-in-mcp-explorer (MCP Explorer)

A native desktop MCP client built entirely on GPUI (retained-mode GPU UI) that consumes the stand-in-client SDK. Inspect and interact with any MCP server, 100% local — no webview, no telemetry.

Design System — stand-in-mcp-explorer-ds

A themed, token-driven component library with 24 components mirroring the canonical Design System in stand-in-client-prototipo/:

  • core/ — Icon (22 glyphs), Button, IconButton, Badge, CopyButton, ToggleLink, StatusDot, Spinner
  • forms/ — Field, KeyValueRow, SegmentedControl, Select
  • navigation/ — SectionLabel, CapChip, Topbar, Tabbar, SidebarShell
  • data/ — Panel, ListItem, ListSearch, PresetCard, LogRow, EmptyState, HintBar, JsonView
  • theme/ — jandi colour palette mapped to gpui-component's ThemeColor, density (compact/regular/comfy), embedded typefaces (Hanken Grotesk + JetBrains Mono)

Gallery Storybook

Explore every component interactively in dark/light mode with density switching:

cargo run -p stand-in-mcp-explorer-gallery

The Explorer app is being rebuilt on this Design System (026). The previous egui Studio has been retired — the new path uses GPUI / gpui-component (retained-mode). The DS crate and gallery are publish = false — they ship as part of the workspace, not as crates.io packages.

Why "stand-in"?

Because good infrastructure disappears.

A stand-in does essential work — they're on set for hours so the real performance can happen in minutes. But you never see them in the final cut. That's exactly what these macros do: they show up in your source code, do the hard work at compile time, and vanish from the binary.

Your code reads like a declaration of intent. The compiler turns it into a server. The stand-in was never in the final cut.

License

MIT

from github.com/franciscotbjr/stand-in

Installing Stand In Macros

This server has no published package — it is built from source. Open the repository and follow its README.

▸ github.com/franciscotbjr/stand-in

FAQ

Is Stand In Macros MCP free?

Yes, Stand In Macros MCP is free — one-click install via Unyly at no cost.

Does Stand In Macros need an API key?

No, Stand In Macros runs without API keys or environment variables.

Is Stand In Macros hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Stand In Macros in Claude Desktop, Claude Code or Cursor?

Open Stand In Macros on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Stand In Macros with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All development MCPs