Fused MCP Agents
Official
by fusedio
{
"cells": [
{
"cell_type": "markdown",
"id": "0878e3d2-d5db-4d98-9691-734d34a65c04",
"metadata": {},
"source": [
"# Step by step: Passing Python functions to Claude Desktop\n",
"\n",
"By the end of this notebook you'll have everything you need to run Claude with a demo Python function!\n",
"\n",
"We made this for ourselves & other data scientists who are more familiar with notebooks & Python to directly pass any Python code to Claude.\n",
"\n",
"This notebook will:\n",
"\n",
"1. Setup all you need to pass existing functions in this repo to Claude\n",
"3. Pass a function giving the time in UTC to Claude to Claude's MCP server config\n",
"4. Automatically restarting Claude with new changes\n",
"\n",
"In [the next notebook we will](create_your_own_agents.ipynb):\n",
"\n",
"1. Show you how to write your own function & save it locally\n",
"2. Create a new Agent with whichever functions you want\n",
"3. Change which (list of) Agent(s) you want Claude to know about\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "30c6f5d0-0da2-41b2-8f95-28438cba2d1c",
"metadata": {},
"outputs": [],
"source": [
"import fused\n",
"import json\n",
"import os\n",
"import time\n",
"from pathlib import Path"
]
},
{
"cell_type": "markdown",
"id": "6c2b99ff",
"metadata": {},
"source": [
"Update the following with your local file paths:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "678261d7-1e7d-4a7c-86af-120e2c2b5a90",
"metadata": {},
"outputs": [],
"source": [
"# This is the config file for MCP Servers that Claude Desktop uses\n",
"PATH_TO_CLAUDE_CONFIG = (\n",
" f\"{str(Path.home())}/Library/Application Support/Claude/claude_desktop_config.json\"\n",
")\n",
"\n",
"\n",
"if not os.path.exists(PATH_TO_CLAUDE_CONFIG):\n",
" # Creating the config file\n",
" os.makedirs(os.path.dirname(PATH_TO_CLAUDE_CONFIG), exist_ok=True)\n",
" with open(PATH_TO_CLAUDE_CONFIG, \"w\") as f:\n",
" json.dump({}, f)\n",
"\n",
"assert os.path.exists(PATH_TO_CLAUDE_CONFIG), (\n",
" \"Please update the PATH_TO_CLAUDE_CONFIG variable with the correct path to your Claude config file\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "505cc4ea",
"metadata": {},
"outputs": [],
"source": [
"# Local path to the Claude app\n",
"CLAUDE_APP_PATH = \"/Applications/Claude.app\"\n",
"assert os.path.exists(CLAUDE_APP_PATH), (\n",
" \"Please update the CLAUDE_APP_PATH variable with the correct path to your Claude app\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "f7b3262e",
"metadata": {},
"outputs": [],
"source": [
"# Change this path if you're not running this from the repo root\n",
"WORKING_DIR = os.getcwd()"
]
},
{
"cell_type": "markdown",
"id": "f3c4215a",
"metadata": {},
"source": [
"## Calling pre-existing Agents (that we built for you)\n",
"\n",
"We've already created a few agents for you in this repo. So we'll start by showing you _how to run existing agents_ and in the [following notebook](create_your_own_agents.ipynb) we'll go over how to create your own and passing your own functions\n",
"\n",
"We built a very simple \"hello world\" example of passing Python functions to an LLM: Getting to know the time!\n",
"\n",
"This might sound like a naive example, but [current LLMs have a training cutoff date](https://github.com/HaoooWang/llm-knowledge-cutoff-dates) after which they have no knowledge of the world. This means that while it might sound simple enough, current LLM models can't know the current time. \n",
"\n",
"We're going to give Claude the ability to know the time by just running a simple Python script inside a Fused UDF that gives the current time"
]
},
{
"cell_type": "markdown",
"id": "7d8e237b",
"metadata": {},
"source": [
"\n",
"\n",
"<!-- TODO: Explain a bit what a UDF (requirements, input / outputs) -->\n",
"<!-- Explain why time is good example for LLMs, because they have a cutoff period of time -->"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "a874de7c-d49d-48db-90c9-35576d620305",
"metadata": {},
"outputs": [],
"source": [
"# As MCP implementation are constantly shifting we've moved some of our implementation code to another repo to make it easier to update this notebook without major changes\n",
"commit = \"5f860ab\"\n",
"common = fused.load(\n",
" f\"https://github.com/fusedio/udfs/tree/{commit}/public/common\"\n",
").utils"
]
},
{
"cell_type": "markdown",
"id": "18b4372a",
"metadata": {},
"source": [
"### Our local `agents.json` file\n",
"\n",
"We've build an intermediate `agents.json` file in this repo that serves as a simple inventory of which `agents` you have available for you to use. \n",
"\n",
"This JSON file contains:\n",
"- `names` -> Name of the agent. This is what we'll easily be able to change down the line to give Claude access to different functions\n",
"- `udfs` -> These are the name of the individual Fused UDFs that a given Agent can call\n",
"\n",
"Think of this as different \"folders\" of capabilities that Claude can have, with different \"files\" of Python functions"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "9a07244e-47f3-43f1-b163-345720c5f6db",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"agents\": [\n",
" {\n",
" \"name\": \"get_current_time\",\n",
" \"udfs\": [\n",
" \"current_utc_time\"\n",
" ]\n",
" },\n",
" {\n",
" \"name\": \"fused_docs\",\n",
" \"udfs\": [\n",
" \"list_public_udfs\",\n",
" \"reading_fused_docs\"\n",
" ]\n",
" },\n",
" {\n",
" \"name\": \"getting_the_news\",\n",
" \"udfs\": [\n",
" \"recent_hacker_news_stories\"\n",
" ]\n",
" }\n",
" ]\n",
"}\n"
]
}
],
"source": [
"# Checking the agents.json file\n",
"agents = json.load(open(os.path.join(WORKING_DIR, \"agents.json\")))\n",
"print(json.dumps(agents, indent=4, sort_keys=True))"
]
},
{
"cell_type": "markdown",
"id": "8e4d87f7",
"metadata": {},
"source": [
"### Generating `claude_desktop_config.json`\n",
"\n",
"Claude Desktop App is expecting a specific JSON file that tells it: what to run, and where. \n",
"\n",
"The following helper function simply edits this `claude_desktop_config.json` under the hood for us and tells Claude which Agent we want to run from the `agents.json` above\n",
"\n",
"For this simple example, we'll use the pre-existing `get_current_time` Agent (which runs the `current_utc_time` UDF as per the `agents.json` file above)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "787b6142-0ad0-401e-a573-225e3cbcc0ea",
"metadata": {},
"outputs": [],
"source": [
"common.generate_local_mcp_config(\n",
" config_path=PATH_TO_CLAUDE_CONFIG,\n",
" agents_list=[\"get_current_time\"],\n",
" repo_path=WORKING_DIR,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "a9bdb274-dc04-49d5-bb42-8ef988a7304a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'mcpServers': {'get_current_time': {'command': 'uv',\n",
" 'args': ['run',\n",
" '--directory',\n",
" '/Users/maximelenormand/Library/CloudStorage/Dropbox/Mac/Documents/repos/fused-mcp',\n",
" 'main.py',\n",
" '--runtime=local',\n",
" '--udf-names=current_utc_time',\n",
" '--name=get_current_time']}}}"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Reading claude config to confirm\n",
"json.load(open(PATH_TO_CLAUDE_CONFIG))"
]
},
{
"cell_type": "markdown",
"id": "b7cdd82c",
"metadata": {},
"source": [
"## Now restart Claude!\n",
"\n",
"Everytime we change the `claude_desktop_config.json` we need to restart Claude so we made a quick util to do it for you!"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "3c1b91f6",
"metadata": {},
"outputs": [],
"source": [
"def restart_claude(claude_path: str = CLAUDE_APP_PATH):\n",
" app_name = claude_path.split(\"/\")[-1]\n",
"\n",
" try:\n",
" os.system(f\"pkill -f '{app_name}'\")\n",
" print(f\"Killed {app_name}\")\n",
" time.sleep(2) # Wait for shutdown\n",
" except Exception:\n",
" print(\"Claude wasn't running, so no need to kill it\")\n",
"\n",
" print(f\"Restarting {app_name}\")\n",
" os.system(f\"open -a '{claude_path}'\") # Restart Claude"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "b66ed08d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Killed Claude.app\n",
"Restarting Claude.app\n"
]
}
],
"source": [
"restart_claude()"
]
},
{
"cell_type": "markdown",
"id": "17fe3491",
"metadata": {},
"source": [
"You might have to wait for a few seconds for Claude to load our MCP tools. Once tools are loaded it looks like this:\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"id": "76b96f28",
"metadata": {},
"source": [
"Everytime you run this you need to click \"Allow For This Chat\":\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"id": "ee899c9f",
"metadata": {},
"source": [
"## Change agents & Build your own!\n",
"\n",
"We've shown you how to get started, in the next notebook [we'll show you how to write your own functions & change agent names](create_your_own_agents.ipynb)"
]
},
{
"cell_type": "markdown",
"id": "d35adf18",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}