diff --git a/ai_agents/anthropic_mcp/README.md b/ai_agents/anthropic_mcp/README.md new file mode 100644 index 00000000..de2fdf60 --- /dev/null +++ b/ai_agents/anthropic_mcp/README.md @@ -0,0 +1,44 @@ +--- +title: Anthropic MCP Sample +description: Simple usage of the Anthropic API via AutoKitteh +integrations: ["anthropic", "slack"] +categories: ["AI", "Productivity"] +--- + +# Anthropic MCP Sample + +[![Start with AutoKitteh](https://autokitteh.com/assets/autokitteh-badge.svg)](https://app.autokitteh.cloud/template?name=ai_agents/anthropic_mcp) + +This sample demonstrates how to interact with Anthropic's API using AutoKitteh, enabling you to leverage advanced language models for your automation workflows. + +It includes functionality to send prompts to Anthropic's Claude models and process the responses. + +API details: + +- [Anthropic API documentation](https://docs.anthropic.com/claude/docs) +- [AutoKitteh Anthropic integration](https://docs.autokitteh.com/integrations/anthropic) + +## How It Works + +1. Sends a prompt to the Anthropic Claude API. +2. Receives and logs the model's response in the AutoKitteh session. + +## Cloud Usage (Recommended) + +1. Initialize your Anthropic connection through the AutoKitteh UI. +2. Copy the webhook trigger's URL (for the [Trigger Workflows](#trigger-workflows) section below): + + - Hover over the trigger's (i) icon for the webhook you want to use. + - Click the copy icon next to the webhook URL for your selected trigger. + - (Detailed instructions + [here](https://docs.autokitteh.com/get_started/deployment#webhook-urls)) + +3. Set any required environment variables or project variables (such as your Anthropic API key). + +## Trigger Workflows + +`send_prompt`: + +```shell +curl -i "${WEBHOOK_URL}" --url-query prompt="Your question for Claude" +``` diff --git a/ai_agents/anthropic_mcp/autokitteh.yaml b/ai_agents/anthropic_mcp/autokitteh.yaml new file mode 100644 index 00000000..da2198d3 --- /dev/null +++ b/ai_agents/anthropic_mcp/autokitteh.yaml @@ -0,0 +1,24 @@ +# This file is part of the MCP project, which integrates with Anthropic and Slack. + +version: v1 + +project: + name: anthropic_mcp + + connections: + - name: slack_conn + integration: slack + + triggers: + - name: send_msg + event_type: message + connection: slack_conn + call: program.py:start_workflow + + vars: + - name: ANTHROPIC_API_KEY + value: "" + - name: SERVER_URL + value: "" + - name: SLACK_CHANNEL + value: "" diff --git a/ai_agents/anthropic_mcp/program.py b/ai_agents/anthropic_mcp/program.py new file mode 100644 index 00000000..61db0a7c --- /dev/null +++ b/ai_agents/anthropic_mcp/program.py @@ -0,0 +1,96 @@ +"""MCPClient integration with Anthropic and Slack. + +This module provides an MCPClient class that connects to a server. +Processes queries using Anthropic's API, and posts results to a Slack channel. +""" + +import asyncio +from contextlib import AsyncExitStack +import os + +from anthropic import Anthropic +import autokitteh +from autokitteh.slack import slack_client +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client + + +slack = slack_client("slack_conn") + +ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") +SERVER_URL = os.getenv("SERVER_URL") +SLACK_CHANNEL = os.getenv("SLACK_CHANNEL") # ID or name. + + +class MCPClient: + """Client for MCP server queries using Anthropic, with results posted to Slack.""" + + def __init__(self): + self.session: ClientSession | None = None + self.exit_stack = AsyncExitStack() + self.anthropic = Anthropic(api_key=ANTHROPIC_API_KEY) + + @autokitteh.activity + async def connect_to_server(self, server_url: str): + transport = await self.exit_stack.enter_async_context( + streamablehttp_client(server_url) + ) + self.read, self.write, _ = transport + self.session = await self.exit_stack.enter_async_context( + ClientSession(self.read, self.write) + ) + await self.session.initialize() + print(f"\nConnected to server at {server_url}") + + @autokitteh.activity + async def process_query(self, query: str) -> str: + messages = [{"role": "user", "content": query}] + tools = (await self.session.list_tools()).tools + available_tools = [ + { + "name": t.name, + "description": t.description, + "input_schema": t.inputSchema, + } + for t in tools + ] + + response = self.anthropic.messages.create( + model="claude-3-5-sonnet-20241022", + max_tokens=1000, + messages=messages, + tools=available_tools, + ) + + for content in response.content: + if content.type == "text": + slack.chat_postMessage(channel=SLACK_CHANNEL, text=content.text) + elif content.type == "tool_use": + result = await self.session.call_tool(content.name, content.input) + if result.content: + text = f"tool result: {result.content[0].text}\n" + slack.chat_postMessage(channel=SLACK_CHANNEL, text=text) + else: + text = "Tool returned no content." + slack.chat_postMessage(channel=SLACK_CHANNEL, text=text) + + async def cleanup(self): + await self.exit_stack.aclose() + + +async def handle_mcp_request(query): + """Handle a request to the MCP server with the given query.""" + client = MCPClient() + try: + await client.connect_to_server(SERVER_URL) + await client.process_query(query) + except (ConnectionError, RuntimeError, ValueError) as e: + print(f"\nError: {str(e)}") + finally: + await client.cleanup() + + +def start_workflow(event): + """Entrypoint for the MCPClient workflow.""" + query = event.data.text + asyncio.run(handle_mcp_request(query)) diff --git a/tests/metadata_definitions.py b/tests/metadata_definitions.py index 0e0d185f..26043378 100644 --- a/tests/metadata_definitions.py +++ b/tests/metadata_definitions.py @@ -3,6 +3,7 @@ METADATA = ("title", "description", "integrations", "categories") ALLOWED_INTEGRATIONS = ( + "anthropic", "asana", "auth0", "autokitteh",