How to Implement Supply Chain Tracking with In-Process Tools and MCP Server Integration
You can implement supply chain tracking by wrapping Python functions with the @tool decorator, exposing them through an MCP server using create_sdk_mcp_server, and integrating with Oracle AI Agent Memory to persist shipment state across conversational turns.
The oracle-devrel/oracle-ai-developer-hub repository provides a complete reference implementation for building AI-powered supply chain assistants using the Claude Agent SDK and Oracle AI Agent Memory. This guide demonstrates how to implement supply chain tracking with in-process tools and MCP server integration, enabling Claude to read and write shipment data while maintaining persistent context across sessions.
Architecture Overview
The implementation relies on three tightly-coupled components working together:
- Claude Agent SDK: Provides the LLM runtime and tool-calling infrastructure. Tools defined with the
@tooldecorator are automatically translated into the Model Context Protocol (MCP) that Claude understands. - Oracle AI Agent Memory (OAMP): A persistent vector store and conversational history layer built on Oracle AI Database. It stores shipment facts and chat threads using the
OracleAgentMemoryclass. - In-Process Tools: Pure async Python functions (e.g.,
list_shipments,update_shipment) that execute business logic and return MCP-compatible payloads.
The architectural flow begins with installing dependencies, establishing a database connection, and registering users and agents with OAMP. You then define tools using the @tool decorator, bundle them into an MCP server via create_sdk_mcp_server, and connect the Claude SDK client to this server. The Runner.run method handles multi-turn conversations where Claude can invoke tools to query or mutate shipment state while OAMP persists context.
Prerequisites and Environment Setup
Install the required packages including the Oracle Agent Memory client with LiteLLM support, the Claude Agent SDK, and utilities for async handling in notebooks.
pip install "oracleagentmemory[litellm]" claude-agent-sdk nest_asyncio
Configure environment variables for API keys and database credentials. Apply nest_asyncio to enable async operations in Jupyter environments.
import os
import nest_asyncio
nest_asyncio.apply()
os.environ.setdefault("ANTHROPIC_API_KEY", "sk-...")
os.environ.setdefault("OPENAI_API_KEY", "sk-...")
os.environ.setdefault("DB_USER", "VECTOR")
os.environ.setdefault("DB_PASSWORD", "VectorPwd_2025")
os.environ.setdefault("DB_CONNECT_STRING", "localhost:1521/FREEPDB1")
Connecting to Oracle AI Database
Establish a connection to the Oracle AI Database using oracledb.connect(), then instantiate the OracleAgentMemory client with an embedding model and extraction LLM. The table_name_prefix="supply_" parameter ensures dedicated namespace isolation for this application.
import oracledb
from oracleagentmemory.core import OracleAgentMemory
from oracleagentmemory.core.llms import Llm
conn = oracledb.connect(
user=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
dsn=os.environ["DB_CONNECT_STRING"],
)
extraction_llm = Llm("gpt-4o-mini", temperature=0.2)
memory_client = OracleAgentMemory(
connection=conn,
embedder="text-embedding-3-small",
llm=extraction_llm,
extract_memories=True,
schema_policy="create_if_necessary",
table_name_prefix="supply_",
)
Register the operator and agent identities so OAMP can associate memories with specific entities.
OPERATOR_ID = "operator-morgan"
AGENT_ID = "supply-chain-assistant"
memory_client.add_user(
OPERATOR_ID,
"Operations manager — West Coast logistics desk."
)
memory_client.add_agent(
AGENT_ID,
"Supply‑chain assistant with read/write tools for shipment state."
)
Defining In-Process Tools
Tools are async functions decorated with @tool(name, description, input_schema) from the claude_agent_sdk package. Each function receives a JSON argument dictionary and must return a dict following the MCP content format: {"content": [{"type": "text", "text": ...}]}.
List Shipments Tool
from claude_agent_sdk import tool
import json
SHIPMENT_DB = {} # Replace with actual ERP or database connection
@tool(
"list_shipments",
"List every shipment currently tracked by the system. Returns a JSON array.",
{}
)
async def list_shipments(_: dict) -> dict:
return {
"content": [
{"type": "text", "text": json.dumps(list(SHIPMENT_DB.values()))}
]
}
Get Shipment Status Tool
@tool(
"get_shipment_status",
"Return the status of a specific shipment identified by its ID.",
{"type": "object", "properties": {"shipment_id": {"type": "string"}}}
)
async def get_shipment_status(args: dict) -> dict:
shipment = SHIPMENT_DB.get(args["shipment_id"])
return {
"content": [
{"type": "text", "text": json.dumps(shipment)}
]
}
Update Shipment Tool
@tool(
"update_shipment",
"Mutate a field on a shipment (e.g. status, eta).",
{
"type": "object",
"properties": {
"shipment_id": {"type": "string"},
"field": {"type": "string"},
"value": {"type": "string"}
}
}
)
async def update_shipment(args: dict) -> dict:
sh = SHIPMENT_DB[args["shipment_id"]]
sh[args["field"]] = args["value"]
return {
"content": [
{"type": "text", "text": "Updated"}
]
}
Recall Operational Notes Tool
@tool(
"recall_operational_notes",
"Semantic search over OAMP for prior notes about a shipment or carrier.",
{"type": "object", "properties": {"query": {"type": "string"}}}
)
async def recall_operational_notes(args: dict) -> dict:
hits = await memory_client.search(args["query"], top_k=5)
return {
"content": [
{"type": "text", "text": json.dumps(hits)}
]
}
Exposing Tools via MCP Server
Bundle the tool functions into an in-process HTTP server using create_sdk_mcp_server. This function abstracts the HTTP/JSON plumbing, handling request serialization and LLM-tool negotiation automatically.
from claude_agent_sdk import create_sdk_mcp_server
supply_server = create_sdk_mcp_server(
name="supply",
tools=[
list_shipments,
get_shipment_status,
update_shipment,
recall_operational_notes,
],
)
The resulting supply_server object implements the Model Context Protocol and exposes the tools over HTTP, allowing the Claude SDK to discover and invoke them without additional routing code.
Building and Running the Assistant
Instantiate a ClaudeSDKClient configured to use the MCP server, then run multi-turn conversations using the Runner class. The session object persists conversation history and memory across turns.
from claude_agent_sdk import ClaudeSDKClient, Runner
from oracleagentmemory.core import OracleAgentMemorySession
supply_agent = ClaudeSDKClient(
name="supply-chain-assistant",
system_prompt="You are a supply‑chain assistant for the West Coast logistics desk.",
llm_provider="anthropic",
llm_model="claude-3-sonnet-20240229",
mcp_servers={"sc": supply_server},
)
session = OracleAgentMemorySession(
"supply-session-001",
memory_client,
OPERATOR_ID,
AGENT_ID
)
# First turn
prompt = "What shipments are currently in transit?"
result = await Runner.run(supply_agent, prompt, session=session)
print(result)
# Second turn - LLM can invoke update_shipment based on context
followup = "Mark shipment SHP-1001 as delivered."
result = await Runner.run(supply_agent, followup, session=session)
print(result)
The Runner.run method automatically handles tool invocation, context stitching, and memory lookups. When the LLM decides to call a tool, the SDK serializes the parameters, executes the Python function, and returns the MCP-formatted result to the model.
Reference Implementation
For a complete, runnable end-to-end example, see the Jupyter notebook at notebooks/agent_memory/02_supply_chain_claude_agent_sdk.ipynb in the oracle-devrel/oracle-ai-developer-hub repository. This notebook demonstrates installation, database setup, tool definition, and conversational loops. An alternative implementation using the OCI-based Agent SDK is available at notebooks/agent_memory/02_supply_chain_oci_sdk.ipynb, demonstrating the interchangeable nature of the MCP pattern across different LLM providers.
Summary
- Tool-first design: Keep business logic in pure Python functions (like
list_shipmentsandupdate_shipment) to maintain clean separation from LLM orchestration code. - MCP abstraction: Use
create_sdk_mcp_serverto automatically expose tools via the Model Context Protocol without handwritten HTTP handlers. - Persistent memory: The
OracleAgentMemoryclass inoracleagentmemory/core.pyprovides both semantic vector search and threaded chat history, enabling continuity across kernel restarts. - Scalability: Launch additional MCP servers to scale tool bundles horizontally, or replace the in-memory
SHIPMENT_DBwith production ERP systems without modifying the agent definition. - Integration points: The pattern supports both Claude and OCI SDK implementations through identical MCP server interfaces.
Frequently Asked Questions
What is an MCP server in this context?
An MCP (Model Context Protocol) server is an HTTP server that exposes tools to Claude using a standardized JSON-RPC protocol. In the oracle-ai-developer-hub implementation, create_sdk_mcp_server wraps your Python functions into this protocol automatically, handling request routing, parameter validation, and response serialization so the LLM can discover and invoke tools dynamically.
How does Oracle AI Agent Memory store shipment data?
OracleAgentMemory stores data in two forms: facts as semantic vectors (using text-embedding-3-small embeddings) and messages as threaded chat history in Oracle AI Database tables. When you call memory_client.search(), it performs vector similarity search over previously extracted memories, allowing the assistant to recall that "Shipment SHP-1002 is at port" even after the Python kernel restarts.
Can I replace the dummy SHIPMENT_DB with a real ERP system?
Yes. Because tools are pure Python functions receiving JSON arguments and returning MCP-formatted dictionaries, you can replace the dictionary lookup in get_shipment_status or update_shipment with API calls to SAP, Oracle SCM Cloud, or custom microservices. The @tool decorator and MCP server infrastructure remain unchanged regardless of the underlying data source.
What is the difference between the Claude SDK and OCI SDK implementations?
Both implementations use identical in-process tools and MCP server architecture, but they differ in the LLM client: 02_supply_chain_claude_agent_sdk.ipynb uses ClaudeSDKClient with Anthropic's Claude models, while 02_supply_chain_oci_sdk.ipynb uses the OCI-based Agent SDK. This demonstrates how the MCP pattern provides portability across different LLM providers while keeping the tool definitions and memory layer constant.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →