AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP unreviewed MIT Self-run

Refract

mcp-brunvelop-refract · by Brunvelop

One Python function → REST API, CLI, MCP tools for AI agents & Web UI. Zero boilerplate.

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add mcp-brunvelop-refract

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Dangerous shell/eval execution.

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

Not yet reviewed
0 installs to date
no reviews yet
5mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Refract? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

refract 💎

> One Python function. Four interfaces. Zero boilerplate.

[](https://www.python.org/downloads/) [](LICENSE) []()

Define a typed Python function once — and automatically get a REST API, a CLI, MCP tools for AI agents, and a Frontend SDK.

from pydantic import BaseModel
from refract import Refract, register_function

class Sum(BaseModel):
    result: int

@register_function()
def add(a: int, b: int) -> Sum:
    """Add two numbers.
    Args:
        a: First number
        b: Second number
    """
    return Sum(result=a + b)

app = Refract("my-project")

That's it. No routers, no argument parsers, no tool definitions.


What you get

| Interface | How to get it | What it gives you | |-----------|--------------|-------------------| | REST API | app.api() | FastAPI app with auto-generated endpoints | | CLI | app.cli() | Click group with serve, list, and one command per function | | MCP tools | app.mcp() | FastAPI + MCP server for AI agents / LLMs | | Frontend SDK | import { RefractClient } from '/refract/client.js' | Typed JS client for browsers and frameworks |


🚀 Quick start

Install:

pip install git+https://github.com/Brunvelop/refract

Try the demo right now:

git clone https://github.com/Brunvelop/refract && cd refract
python demo.py serve          # API + MCP + Web UI at http://localhost:8000
python demo.py list           # List all registered functions
python demo.py add --a 3 --b 5
python demo.py greet --name World

🧩 Define functions

Every function follows the same pattern: typed signature + docstring + BaseModel return type.

from pydantic import BaseModel
from refract import register_function

class SearchResponse(BaseModel):
    items: list[str]
    total: int

@register_function(http_methods=["GET"])
def search(query: str, limit: int = 10) -> SearchResponse:
    """Search items.
    Args:
        query: Search term
        limit: Maximum number of results
    """
    results = ["foo", "bar", "baz"][:limit]
    return SearchResponse(items=results, total=len(results))

The return type becomes the FastAPI response_model — precise OpenAPI schema, full type safety, and the exact same shape on every interface.

Decorator options

@register_function(
    http_methods=["GET", "POST"],   # default: ["GET", "POST"]
    interfaces=["api", "cli"],      # default: ["api", "cli", "mcp"]
    streaming=False,                # default: False
    stream_func=None,               # required if streaming=True
)

> Note: async def functions are fully supported on API and MCP interfaces — they will be properly awaited. The CLI interface only supports synchronous functions; async functions are automatically skipped with a warning at CLI build time.

> CLI limitations: Parameters with complex types (list, dict, List[str], Dict[str, int], etc.) fall back to plain strings in the CLI interface. Click only natively supports int, float, bool, and str. For functions that require structured inputs, consider restricting them to interfaces=["api", "mcp"], or accept a JSON string and parse it inside the function.


📐 Setup modes

The setup scales progressively. Each mode adds one or two lines — never a new file.

Mode 1 — One line (recommended default)

# my_project/app.py
from refract import Refract

app = Refract("my-project", discover=["my_project.core"])

Wire it up as a CLI entry point:

# pyproject.toml
[project.scripts]
my-project = "my_project.app:app.run_cli"

You immediately get:

my-project serve          # Start unified server (API + MCP + UI) at http://0.0.0.0:8000
my-project serve-api      # Start REST API only at http://127.0.0.1:8000
my-project serve-mcp      # Start MCP-only server at http://127.0.0.1:8001
my-project list           # List all registered functions
my-project add --a 1 --b 2   # Call any registered function directly
my-project --verbose serve   # Enable DEBUG logging

> No boilerplate. The discover= list tells Refract which packages to scan for @register_function decorators. Everything else is automatic.

> Custom views and static assets — Pass views to replace the default dashboard with your own HTML pages, and static_dirs to serve additional static directories: > > ``python > app = Refract("my-project", > discover=["my_project.core"], > views={"/": "templates/index.html", "/about": "templates/about.html"}, > static_dirs=[("/static", "my_app/static")], > ) > ` > > The /refract/ namespace is reserved for the SDK JS files and must not be used in static_dirs`.

> Advanced uvicorn options — The built-in serve commands are convenience wrappers that support --host and --port. Features like auto-reload, multiple workers, or custom log levels require uvicorn to be invoked with a string import path pointing to an ASGI application. app.run_cli is a Click group, not an ASGI app — expose app.api() as a module-level variable instead: > > ``python > # my_project/app.py > from refract import Refract > > app = Refract("my-project", discover=["my_project.core"]) > fastapi_app = app.api() # ASGI app — this is what uvicorn needs > ` > > `bash > # Auto-reload in development > uvicorn my_project.app:fastapi_app --reload > > # Multiple workers for production > uvicorn my_project.app:fastapi_app --workers 4 --host 0.0.0.0 --port 8000 > > # Custom log level > uvicorn my_project.app:fastapi_app --log-level warning > `` > > See the uvicorn docs for the full list of available options.

Mode 2 — Custom CLI commands

Same file, add @app.command():

# my_project/app.py
from refract import Refract

app = Refract("my-project", discover=["my_project.core"])

@app.command()
def health_check():
    """Run project health checks."""
    import subprocess
    subprocess.run(["pytest", "tests/health/", "-q"])
my-project health-check   # Your custom command, alongside serve/list

Custom commands use Click under the hood — click.echo, click.option, etc. work normally.

Mode 3 — Bring your own FastAPI app

Use app.router() to mount only the function endpoints onto your own FastAPI instance:

# my_project/app.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from refract import Refract

refract_app = Refract("my-project", discover=["my_project.core"])

my_app = FastAPI(title="My Project", version="1.0.0")
my_app.add_middleware(CORSMiddleware, allow_origins=["*"])
my_app.include_router(refract_app.router())

@my_app.get("/status")
def status():
    return {"ok": True}

app.router() includes:

  • POST/GET /{funcName} — one endpoint per registered function
  • GET /functions/details — JSON Schema discovery for the frontend
  • GET /health — basic health check

MCP sidecar — mcp_only()

For dedicated AI agent deployments, use mcp_only() to spin up a minimal FastAPI app with only MCP tool endpoints and a /health check — no REST API, no HTML pages, no static files:

# Sidecar deployment: MCP tools without a full REST API
mcp_app = app.mcp_only()

This is also what serve-mcp uses under the hood, making it ideal for a separate MCP process alongside an existing REST API server.

Accessing the current instance — Refract.current()

Following the Flask current_app pattern, Refract.current() returns the most recently created instance. Useful in business-logic modules that must not import the app module directly (to avoid circular imports):

from refract import Refract

# In a module that must not import app.py:
instance = Refract.current()
mcp_functions = instance.get_functions_for_interface("mcp")

📡 Streaming (SSE)

For long-running functions (LLM calls, background jobs), use Server-Sent Events:

import json
import asyncio
from pydantic import BaseModel
from refract import register_function
from refract.sse import format_sse

class ProcessResult(BaseModel):
    text: str

async def _stream_process(text: str):
    """Async generator that yields SSE-formatted events."""
    words = text.split()
    for word in words:
        await asyncio.sleep(0.1)
        yield format_sse("token", json.dumps({"chunk": word}))
    yield format_sse("complete", json.dumps({"message": "done"}))

@register_function(streaming=True, stream_func=_stream_process)
def process_text(text: str) -> ProcessResult:
    """Process text word by word.
    Args:
        text: Text to process
    """
    return ProcessResult(text=text)

On the frontend, consume it with RefractClient.stream():

import { RefractClient } from '/refract/client.js';
const api = new RefractClient();

for await (const { event, data } of api.stream('process_text', { text: 'hello world' })) {
    if (event === 'token') console.log(data.chunk);
    if (event === 'complete') console.log('Done:', data.message);
}

🌐 Frontend

RefractClient (vanilla JS, no framework)

Pure HTTP client for calling Refract functions from JavaScript. Works in any context — vanilla JS, React, Vue, Lit, tests.

import { RefractClient } from '/refract/client.js';

const api = new RefractClient();

// Call a function — schemas are auto-loaded and cached on the first call
const data = await api.call('add', { a: 1, b: 2 });
// → { result: 3 }  — your Pydantic model, as returned by the API

// Stream (SSE)
for await (const { event, data } of api.stream('process', { text: 'hello' })) {
    if (event === 'token')    console.log(data.chunk);
    if (event === 'complete') console.log('Done');
}

// Access schemas (type info from Python)
await api.loadSchemas();
const schema = api.getSchema('add');
// schema.parameters    → [{ name: 'a', type: 'int', required: true }, ...]
// schema.response_schema → { properties: { result: { type: 'integer' } } }

// Validate before calling (for form UX)
const { valid, errors } = api.validate('add', { a: 1 });
// → { valid: false, errors: { b: 'Required' } }

Parameters are validated against Python type definitions before each call (opt-in via { validate: true }). No type coercion — pass the correct JS types matching your Python signatures. The response is returned as-is: it is the serialised form of your Pydantic model.


🖥 Dashboard

app.api() (and serve) include a built-in dashboard at / and /functions. No configuration needed:

  • Health badge — live status from GET /health (function count + healthy/unreachable)
  • Quick links — one-click access to Swagger UI, ReDoc, MCP endpoint, Health check, and Schema JSON
  • Registry table — all registered functions with name, description, HTTP methods, and interface badges (API / CLI / MCP / SSE)
  • MCP panel — the full endpoint URL and connection string (with copy buttons), automatically hidden when MCP is not mounted

For interactive testing of your functions, use Swagger UI (/docs) — it's purpose-built for that.


🔗 Schema sharing (back ↔ front)

Every function schema includes a response_schema — the JSON Schema generated by Pydantic from the return type. This is the contract between backend and frontend.

GET /functions/details
{
  "functions": {
    "add": {
      "name": "add",
      "description": "Add two numbers.",
      "http_methods": ["GET", "POST"],
      "parameters": [
        { "name": "a", "type": "int", "required": true, "description": "First number" },
        { "name": "b", "type": "int", "required": true, "description": "Second number" }
      ],
      "streaming": false,
      "response_schema": {
        "properties": {
          "result": { "title": "Result", "type": "integer" }
        },
        "required": ["result"],
        "title": "Sum",
        "type": "object"
      }
    }
  }
}

The frontend always knows the exact shape of the response — enabling runtime validation, type-safe consumers, and future codegen.


🔍 Discovery logging

Discovery is lazy — modules are imported on the first registry query, not at instantiation time. When Refract scans your packages, you see exactly what happened:

[refract:my-project] Scanning my_project.core...
[refract:my-project]   ✅ my_project.core.math — 2 functions
[refract:my-project]   ✅ my_project.core.search — 1 function
[refract:my-project]   ⚠️  my_project.core.ai — skipped (ImportError: dspy not installed)
[refract:my-project]   ℹ️  my_project.core.models — no @register_function found
[refract:my-project] Total: 3 functions registered, 1 module skipped

Discovery is resilient by default — import errors are logged and skipped. Use --verbose to enable DEBUG-level output.


🏗 Architecture

refract/
├── refract/
│   ├── __init__.py       # Public API: Refract, register_function (version falls back to `0.0.0-dev` when not installed)
│   ├── models.py         # ParamSchema, FunctionInfo, FunctionSchema
│   ├── registry.py       # @register_function decorator + Registry class
│   ├── refract.py        # Refract facade class
│   ├── api.py            # FastAPI app/router factories
│   ├── cli.py            # Click group factory
│   ├── mcp.py            # FastAPI + MCP factory
│   ├── sse.py            # format_sse(), _create_stream_handler()
│   ├── log_config.py     # configure_cli_logging(), configure_api_logging()
│   └── web/
│       ├── client.js     # RefractClient — typed JS client (vanilla JS, no framework)
│       └── views/
│           └── dashboard.html  # Default web UI (served at / and /functions)
├── demo.py               # Runnable demo — try it right now
└── tests/
    ├── conftest.py
    ├── test_models.py
    ├── test_registry.py
    ├── test_api.py
    ├── test_cli.py
    ├── test_mcp.py
    └── test_sse.py

📖 API Reference

Refract(name, discover=None, views=None, static_dirs=None)

| Parameter | Type | Default | Description | |---|---|---|---| | name | str | — | Human-readable name for this instance (used in logs and API title) | | discover | list[str] \| None | None | Package paths to scan for @register_function decorators | | views | dict[str, str] \| None | None | URL path → HTML file path mapping; replaces the default dashboard | | static_dirs | list[tuple[str, str]] \| None | None | Extra (mount_path, directory) pairs for static file serving |

| Method / Property | Returns | Description | |---|---|---| | .api() | FastAPI | Complete FastAPI app with static files and HTML views | | .router() | APIRouter | Only the function endpoints (mount in your own app) | | .cli() | click.Group | Click group with built-in and function commands | | .mcp() | FastAPI | FastAPI app with full API + MCP integration | | .mcp_only() | FastAPI | MCP-only FastAPI app (no REST API, no UI, no static files) | | .run_cli | click.Group | Cached property — use as pyproject.toml entry point | | @.command(name=None, **kwargs) | decorator | Register a custom Click command; name defaults to the function name with _- | | .current() | Registry | Classmethod — returns the most recently created instance (Flask current_app pattern) | | .get_all_functions() | list[FunctionInfo] | All registered functions | | .get_all_schemas() | list[FunctionSchema] | Serialisable schemas (JSON-safe) | | .get_function_by_name(name) | FunctionInfo \| None | Look up a function by name | | .function_count() | int | Number of registered functions |

register_function(...)

| Parameter | Type | Default | Description | |---|---|---|---| | http_methods | list[str] | ["GET", "POST"] | HTTP verbs to expose on the API | | interfaces | list[str] | ["api", "cli", "mcp"] | Which interfaces to enable | | streaming | bool | False | Enable S

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.