Summary:
This blog demonstrates how to plug local functions (“tools”) into an AI model using the Model Context Protocol (MCP). The server exposes five tools spanning multiple domains:
– Weather & Sentiment: Real-time weather data and text sentiment analysis
– DevOps Automation: Jenkins pipeline discovery, triggering, and status monitoring
The client communicates with a hosted LLM (gpt-4o via SAP GenAI Hub) which intelligently decides when to call tools. The workflow: User asks → Model analyzes → Calls necessary tools → Client executes → Results fed back → Model synthesizes answer. This eliminates hard-coded logic and enables the model to handle complex, multi-step operations like “trigger a deployment and monitor its status.”
Goal: Prove that we can build an expanding library of domain specific tools that the model can use safely, in the right context, and in a consistent way, effectively connecting conversational AI with real world systems such as APIs, CI CD pipelines, and databases.
What is MCP?
MCP acts like a universal adapter that enables AI to connect seamlessly to databases, APIs, files, Jenkins, weather services, and more without needing custom integrations each time.
It tells the AI:
What tools existWhat inputs they needHow to call them
Tools – callable functions with input and output
Resources – named pieces of data you can fetch
Prompts – pre-defined message templates
It also defines how the client connects (we use Server-Sent Events, SSE) and a standard way to list tools and call them. Think of it as a “plug board”: you publish capabilities once; any MCP-aware client can use them.
Decouples model orchestration from implementation specifics.Enables multi-tool growth without rewriting client logic.Clear schemas instead of free-form prompt hacks: tools declare input schema, descriptions, and return structured content.Less hallucination: model asks for real data when neededDomain expansion:Adding Jenkins tools required no changes to the client logic and only involved registering the tools on the server side.Security: Authentication and API keys isolated in server environment variables, never exposed to model.Communication:
Problem it tackles:
Problem
Old way
MCP Way
New tool integration
Each new tool requires custom model prompting and wiring
Register tool once; client auto-discoversMessy system prompts
Difficult to maintain system instructions
Prompts are first-class objects
Model hallucination on domain data
LLM invents data instead of fetching
Tools provide authoritative retrieval
Growing code complexityLogic spread everywhere
Clear lifecycle: initialize → list tools → call tool → respond
Different clients behave differently
Each client handles tools differently
Shared protocol = same behavior
Components of MCP:
MCP Server:
The process that defines and publishes capabilities. This is server.py using FastMCP. It registers:
Tools (callable functions)Resources (addressable data via URIs)Prompts (predefined message templates)
Transport: The communication channel between client and server. We’re using HTTP Server‑Sent Events (SSE) with endpoints at ‘/mcp’ (main connection) and ‘/messages/’ (message handling) to stream messages without a full WebSocket stack.
Tools: Functions exposed to the model with a name, description, and JSON input schema. Example: get_weather(location) and sentiment_analysis(text). Tools return structured content the model can reference.
Resources: Read-only, addressable pieces of data identified by URI patterns, e.g. weather://{location}. They give the model a way to “fetch” contextual data without calling a tool.
Prompts: Server-provided starter or templated messages (like initial instructions). They standardize how conversations begin instead of copy-pasting system prompts in each client.
MCP Client:
The consumer that connects to the MCP server, discovers tools/resources/prompts, and manages the conversation. Our client.py handles:
Initialization (list tools, get prompts)Maintaining message historyDispatching tool calls returned by the model
Model / LLM Layer:
The hosted language model (gpt-4o via SAP GenAI Hub in our case). It receives:
Conversation messagesTool schemas
And may respond with either plain text or a set of tool_calls.
Workflow of MCP:
Detailed Steps:
User enters: “What is the weather in San Fransisco?”Client sends full message history + tool schemas to model.Model emits a `tool_calls` array referencing `get_weather` with JSON arguments.Client invokes MCP server’s `get_weather(location=”San Fransisco”)`.Tool returns weather data (via API key or fallback).Client adds a `role: tool` message with result and re-calls model.Model produces final natural language answer including weather details.Final synthesized response displayed to user
Detailed Steps (Jenkins Multi-Tool Example):
User enters: “Trigger the deploy-app pipeline with version 1.5.0″Client sends message + all five tool schemas to model.Model decides to call `get_jenkins_job_info(“deploy-app”)` first (following system prompt guidelines).Client invokes tool → receives parameter definitions: {“VERSION”: “string”, “ENV”: “string”}.Client adds tool result to message history and re-queries model.Model recognizes VERSION is provided (1.5.0) but ENV is missing.Model asks user: “Which environment should I deploy to? (staging/prod)”User responds: “staging”Model now calls `trigger_jenkins_pipeline(“deploy-app”, {“VERSION”: “1.5.0”, “ENV”: “staging”})`.Client invokes tool → Jenkins returns queue_id and build_number.Client appends result and re-queries.Model synthesizes: “Deployment triggered successfully. Build #42 is queued. I can check the status for you.”User: “Yes, check the status”Model calls `get_jenkins_build_status(“deploy-app”)`.Tool returns: {“building”: true, “result”: “IN_PROGRESS”}Model responds: “Build #42 is currently in progress. I’ll let you know when it completes.”
Overview of implementation:
Server (`server.py`) :
– Built with FastMCP + Starlette SSE transport.
– Exposes five tools:
– `get_weather(location)` – real weather data via OpenWeatherMap API.
– `sentiment_analysis(text)` – polarity, subjectivity, assessment via TextBlob.
– `get_jenkins_job_info(job_name)` – retrieves Jenkins job metadata including parameter definitions, buildability, and job URL.
– `trigger_jenkins_pipeline(job_name, parameters)` – triggers a Jenkins pipeline/job with optional build parameters. Returns queue ID and build number.
– `get_jenkins_build_status(job_name)` – fetches the latest build status for a given Jenkins job, including result, duration, and whether it’s still in progress.
– Provides resource: `weather://{location}` (example of resource addressing).
– Prompts: Weather and sentiment prompt templates + initial system/user prompt.Client (`client.py`) :
– Connects to SSE endpoint (`/mcp`) of MCP server.
– Retrieves tools and initial prompts.
– Uses SAP GenAI Hub OpenAI-compatible model (`gpt-4o`).
– For each user query: sends message history; if the model returns `tool_calls`, executes them, appends results, and re-queries until a final answer.
– Implements auto-reconnect logic: if tool execution fails due to connection issues, automatically reconnects to the SSE server and retries.
– Custom system prompt includes guidelines for tool usage, conciseness, and specific Jenkins workflow instructions (always fetch job info before triggering).Auxiliary Files
– `utils.py` – helper class (`TextContent`) for normalizing tool response content.
– `README.md` – usage and architecture overview.
– `requirements.txt` – Python dependencies: fastmcp, textblob, requests, python-dotenv, sap-ai-sdk-gen[all], starlette, uvicorn.
– `.env` – environment configuration for API keys and Jenkins credentials (WEATHER_API_KEY, JENKINS_URL, JENKINS_USER, JENKINS_TOKEN).
– `Dockerfile` – containerization manifest for deploying server to Kyma runtime.
– `.dockerignore` – excludes unnecessary files from Docker build context.
– `YAML/` – directory containing Kubernetes manifests for Kyma deployment:
– `deployment.yaml` – defines the MCP server deployment with container specifications, environment variables from secrets, and service configuration.
– `APIRule.yaml` – configures external access to the MCP server through Kyma gateway with routing rules.
– `secret.yaml` – stores sensitive configuration data (API keys, Jenkins credentials) as Kubernetes secrets.
Deployment Overview:
Local Server & Client: In local development the server is a plain Python process exposing its SSE endpoint. The client runs on the same machine and connects directly to that endpoint. Tool discovery and prompt retrieval happen once at startup; every query reuses the already-fetched tool list. Environment variables (like the SSE URL and optional weather API key) are set in a shell or `.env` file. This mode focuses on fast iteration and straightforward debugging, allowing you to change a tool, restart, and test immediately. The state, including message history, is temporary and logging is written directly to the console.
Containerization Bridge: To move from local to a platform, the server is packaged as a container image. The image bundles the code, Python runtime, and dependencies, and defines a simple entrypoint. Running as a non-root user and keeping the image small improves security and startup speed. Containerization gives a reproducible artifact: the same image tested locally is promoted unchanged to higher environments.
Kyma Runtime Deployment: On SAP BTP Kyma (Kubernetes-based), the server runs as a stateless microservice. A deployment manages replicas of the container; a service offers stable internal access; an API exposure layer (APIRule) maps a public host to the internal service so external clients can reach the SSE endpoint. Configuration such as API keys moves into secrets instead of being baked into the image. Scaling up is a matter of increasing replica count; Kyma handles restarts and scheduling automatically.
Differences From Local: Local is a single process with manual restarts and ad hoc environment settings. Kyma adds orchestration, declarative manifests, automatic recovery, and a controlled perimeter for external access. Observability (central logs, future metrics) and security (non-root, secret management, potential auth guards) are platform benefits. The application logic, including tool registration and query flow, remains the same, and only the execution environment and location change.
Client Against Kyma: When the server is deployed on Kyma the client only needs a different SSE URL (pointing to the public host). The protocol sequence (initialize → list tools → prompt retrieval → tool calls) stays identical, demonstrating that MCP cleanly separates capability discovery from deployment specifics.
Stay Tuned for Part 2 in which , we will dive into MCP integration with Joule Studio using the Joule Agent, demonstrating how AI tools can connect and operate seamlessly within that environment.
Summary:This blog demonstrates how to plug local functions (“tools”) into an AI model using the Model Context Protocol (MCP). The server exposes five tools spanning multiple domains:- Weather & Sentiment: Real-time weather data and text sentiment analysis- DevOps Automation: Jenkins pipeline discovery, triggering, and status monitoringThe client communicates with a hosted LLM (gpt-4o via SAP GenAI Hub) which intelligently decides when to call tools. The workflow: User asks → Model analyzes → Calls necessary tools → Client executes → Results fed back → Model synthesizes answer. This eliminates hard-coded logic and enables the model to handle complex, multi-step operations like “trigger a deployment and monitor its status.”Goal: Prove that we can build an expanding library of domain specific tools that the model can use safely, in the right context, and in a consistent way, effectively connecting conversational AI with real world systems such as APIs, CI CD pipelines, and databases. What is MCP?MCP acts like a universal adapter that enables AI to connect seamlessly to databases, APIs, files, Jenkins, weather services, and more without needing custom integrations each time.It tells the AI:What tools existWhat inputs they needHow to call themTools – callable functions with input and outputResources – named pieces of data you can fetchPrompts – pre-defined message templatesIt also defines how the client connects (we use Server-Sent Events, SSE) and a standard way to list tools and call them. Think of it as a “plug board”: you publish capabilities once; any MCP-aware client can use them. Why?Decouples model orchestration from implementation specifics.Enables multi-tool growth without rewriting client logic.Clear schemas instead of free-form prompt hacks: tools declare input schema, descriptions, and return structured content.Less hallucination: model asks for real data when neededDomain expansion:Adding Jenkins tools required no changes to the client logic and only involved registering the tools on the server side.Security: Authentication and API keys isolated in server environment variables, never exposed to model.Communication:Problem it tackles:ProblemOld wayMCP WayNew tool integrationEach new tool requires custom model prompting and wiringRegister tool once; client auto-discoversMessy system promptsDifficult to maintain system instructionsPrompts are first-class objectsModel hallucination on domain dataLLM invents data instead of fetchingTools provide authoritative retrievalGrowing code complexityLogic spread everywhereClear lifecycle: initialize → list tools → call tool → respondDifferent clients behave differentlyEach client handles tools differentlyShared protocol = same behaviorComponents of MCP:MCP Server:The process that defines and publishes capabilities. This is server.py using FastMCP. It registers:Tools (callable functions)Resources (addressable data via URIs)Prompts (predefined message templates) Transport: The communication channel between client and server. We’re using HTTP Server‑Sent Events (SSE) with endpoints at ‘/mcp’ (main connection) and ‘/messages/’ (message handling) to stream messages without a full WebSocket stack.Tools: Functions exposed to the model with a name, description, and JSON input schema. Example: get_weather(location) and sentiment_analysis(text). Tools return structured content the model can reference.Resources: Read-only, addressable pieces of data identified by URI patterns, e.g. weather://{location}. They give the model a way to “fetch” contextual data without calling a tool.Prompts: Server-provided starter or templated messages (like initial instructions). They standardize how conversations begin instead of copy-pasting system prompts in each client.MCP Client:The consumer that connects to the MCP server, discovers tools/resources/prompts, and manages the conversation. Our client.py handles:Initialization (list tools, get prompts)Maintaining message historyDispatching tool calls returned by the modelModel / LLM Layer:The hosted language model (gpt-4o via SAP GenAI Hub in our case). It receives:Conversation messagesTool schemasAnd may respond with either plain text or a set of tool_calls. Workflow of MCP:Detailed Steps:User enters: “What is the weather in San Fransisco?”Client sends full message history + tool schemas to model.Model emits a `tool_calls` array referencing `get_weather` with JSON arguments.Client invokes MCP server’s `get_weather(location=”San Fransisco”)`.Tool returns weather data (via API key or fallback).Client adds a `role: tool` message with result and re-calls model.Model produces final natural language answer including weather details.Final synthesized response displayed to userDetailed Steps (Jenkins Multi-Tool Example):User enters: “Trigger the deploy-app pipeline with version 1.5.0″Client sends message + all five tool schemas to model.Model decides to call `get_jenkins_job_info(“deploy-app”)` first (following system prompt guidelines).Client invokes tool → receives parameter definitions: {“VERSION”: “string”, “ENV”: “string”}.Client adds tool result to message history and re-queries model.Model recognizes VERSION is provided (1.5.0) but ENV is missing.Model asks user: “Which environment should I deploy to? (staging/prod)”User responds: “staging”Model now calls `trigger_jenkins_pipeline(“deploy-app”, {“VERSION”: “1.5.0”, “ENV”: “staging”})`.Client invokes tool → Jenkins returns queue_id and build_number.Client appends result and re-queries.Model synthesizes: “Deployment triggered successfully. Build #42 is queued. I can check the status for you.”User: “Yes, check the status”Model calls `get_jenkins_build_status(“deploy-app”)`.Tool returns: {“building”: true, “result”: “IN_PROGRESS”}Model responds: “Build #42 is currently in progress. I’ll let you know when it completes.”Overview of implementation:Server (`server.py`) :- Built with FastMCP + Starlette SSE transport.- Exposes five tools:- `get_weather(location)` – real weather data via OpenWeatherMap API.- `sentiment_analysis(text)` – polarity, subjectivity, assessment via TextBlob.- `get_jenkins_job_info(job_name)` – retrieves Jenkins job metadata including parameter definitions, buildability, and job URL.- `trigger_jenkins_pipeline(job_name, parameters)` – triggers a Jenkins pipeline/job with optional build parameters. Returns queue ID and build number.- `get_jenkins_build_status(job_name)` – fetches the latest build status for a given Jenkins job, including result, duration, and whether it’s still in progress.- Provides resource: `weather://{location}` (example of resource addressing).- Prompts: Weather and sentiment prompt templates + initial system/user prompt.Client (`client.py`) :- Connects to SSE endpoint (`/mcp`) of MCP server.- Retrieves tools and initial prompts.- Uses SAP GenAI Hub OpenAI-compatible model (`gpt-4o`).- For each user query: sends message history; if the model returns `tool_calls`, executes them, appends results, and re-queries until a final answer.- Implements auto-reconnect logic: if tool execution fails due to connection issues, automatically reconnects to the SSE server and retries.- Custom system prompt includes guidelines for tool usage, conciseness, and specific Jenkins workflow instructions (always fetch job info before triggering).Auxiliary Files- `utils.py` – helper class (`TextContent`) for normalizing tool response content.- `README.md` – usage and architecture overview.- `requirements.txt` – Python dependencies: fastmcp, textblob, requests, python-dotenv, sap-ai-sdk-gen[all], starlette, uvicorn.- `.env` – environment configuration for API keys and Jenkins credentials (WEATHER_API_KEY, JENKINS_URL, JENKINS_USER, JENKINS_TOKEN).- `Dockerfile` – containerization manifest for deploying server to Kyma runtime.- `.dockerignore` – excludes unnecessary files from Docker build context.- `YAML/` – directory containing Kubernetes manifests for Kyma deployment: – `deployment.yaml` – defines the MCP server deployment with container specifications, environment variables from secrets, and service configuration. – `APIRule.yaml` – configures external access to the MCP server through Kyma gateway with routing rules. – `secret.yaml` – stores sensitive configuration data (API keys, Jenkins credentials) as Kubernetes secrets.Deployment Overview:Local Server & Client: In local development the server is a plain Python process exposing its SSE endpoint. The client runs on the same machine and connects directly to that endpoint. Tool discovery and prompt retrieval happen once at startup; every query reuses the already-fetched tool list. Environment variables (like the SSE URL and optional weather API key) are set in a shell or `.env` file. This mode focuses on fast iteration and straightforward debugging, allowing you to change a tool, restart, and test immediately. The state, including message history, is temporary and logging is written directly to the console.Containerization Bridge: To move from local to a platform, the server is packaged as a container image. The image bundles the code, Python runtime, and dependencies, and defines a simple entrypoint. Running as a non-root user and keeping the image small improves security and startup speed. Containerization gives a reproducible artifact: the same image tested locally is promoted unchanged to higher environments.Kyma Runtime Deployment: On SAP BTP Kyma (Kubernetes-based), the server runs as a stateless microservice. A deployment manages replicas of the container; a service offers stable internal access; an API exposure layer (APIRule) maps a public host to the internal service so external clients can reach the SSE endpoint. Configuration such as API keys moves into secrets instead of being baked into the image. Scaling up is a matter of increasing replica count; Kyma handles restarts and scheduling automatically.Differences From Local: Local is a single process with manual restarts and ad hoc environment settings. Kyma adds orchestration, declarative manifests, automatic recovery, and a controlled perimeter for external access. Observability (central logs, future metrics) and security (non-root, secret management, potential auth guards) are platform benefits. The application logic, including tool registration and query flow, remains the same, and only the execution environment and location change.Client Against Kyma: When the server is deployed on Kyma the client only needs a different SSE URL (pointing to the public host). The protocol sequence (initialize → list tools → prompt retrieval → tool calls) stays identical, demonstrating that MCP cleanly separates capability discovery from deployment specifics.Stay Tuned for Part 2 in which , we will dive into MCP integration with Joule Studio using the Joule Agent, demonstrating how AI tools can connect and operate seamlessly within that environment. Read More Technology Blog Posts by SAP articles
#SAP
#SAPTechnologyblog