Charmbracelet Crush¶
Charmbracelet Crush is the glamourous coding agent. More about it can be found in Charm Land. Here we outline a minimial configuration to use Crush with TU Wien's Acqueduct Infrastructure.
Configuring Crush¶
Crush looks for a configuaration file either in the folder you call it from,
crush.json or in your home folder ~/.config/crush/crush.json.
{
"$schema": "https://charm.land/crush.json",
"providers": {
"ollama" : {
"name": "aqueduct",
"base_url" : "https://aqueduct.ai.datalab.tuwien.ac.at/",
"api_key" : "sk-PASTE-YOUR-GLAMOUROUS-AQUEDUCT-KEY-HERE",
"type" : "openai",
"models" :
[
{
"name" : "qwen-3.5-35b",
"id" : "qwen-3.5-35b",
"context_window" : 130000,
"default_max_tokens" : 50000,
},
{
"name" : "qwen-3.5-397b",
"id" : "qwen-3.5-397b",
"context_window" : 130000,
"default_max_tokens" : 50000,
}
]
}
}
}
Ctrl+l then lets you change models at runtime inside crush.
context_window is here set to about half the actual context window of the
models. It is common for Crush to overshoot the context window. Lowering it
assures summerization passes are triggered earlier, hopefully preventing
context window exhaustion.
Adding Aqueduct Hosted MCP Servers to Crush:¶
The Aqueduct Website contains several MCP servers that you might want to use
inside Crush, such as TU Website search or the Campus IT Service Knowledge Base.
However Crush is not fully compatible with the way Aqueduct hosted MCP Servers
are handled. As a workaround you might use the following wrapper script, adapting BASE_URL to the url of the MCP and AUTH_HEADER with your authentication token:
#!/usr/bin/env python3
"""
MCP stdio wrapper for aqueduct MCP HTTP endpoints.
Translates stdio MCP protocol to HTTP Streamable protocol.
"""
import json
import sys
import requests
BASE_URL = "https://aqueduct.ai.datalab.tuwien.ac.at/mcp-servers/tu-search/mcp"
AUTH_HEADER = "Bearer sk-PASTE-YOUR-GLAMOUROUS-AQUEDUCT-KEY-HERE"
session = requests.Session()
session.headers.update({
"Authorization": AUTH_HEADER,
"Content-Type": "application/json",
})
session_id = None
def send_request(payload):
global session_id
headers = dict(session.headers)
if session_id:
headers["Mcp-Session-Id"] = session_id
resp = session.post(BASE_URL, headers=headers, json=payload, timeout=30)
# Capture session ID from response
new_session_id = resp.headers.get("Mcp-Session-Id")
if new_session_id:
session_id = new_session_id
return resp.json()
def main():
for line in sys.stdin:
try:
msg = json.loads(line.strip())
method = msg.get("method", "")
# Handle notifications/initialized
if method == "notifications/initialized":
send_request(msg)
# Don't respond to notifications
# Handle ping
elif method == "ping":
print(json.dumps({"jsonrpc": "2.0", "id": msg.get("id"), "result": {}}), flush=True)
else:
# Handle all other possible methods with direct passthrough
result = send_request(msg)
print(json.dumps(result), flush=True)
except Exception as e:
error = {
"jsonrpc": "2.0",
"id": msg.get("id") if 'msg' in locals() else None,
"error": {"code": -32603, "message": str(e)}
}
print(json.dumps(error), flush=True)
if __name__ == "__main__":
main()
The MCP server can than be added to your crush.json config file:
{
"$schema": "https://charm.land/crush.json",
"providers": {
...
},
"mcp": {
"tu-search": {
"type": "stdio",
"command": "python3",
"args": ["/path/to/the/wrapper/script/above.py"]
}
}
}
Tip
Crush has a skill to allow you to understand and edit its config file from within crush. If you want to modify the crush.json file, just ask crush what you wish to do. I.e. ask for limiting commands to be executed, adding hooks and all kind of other things.
Tip
In case you want to configure self written MCPs and automate custom workflows with crush, we provide a video for that: https://www.youtube.com/watch?v=L7OWwEOmlZw