# How to Implement Supply Chain Tracking with In-Process Tools and MCP Server Integration

> Learn to implement supply chain tracking by decorating Python functions with @tool, exposing them via an MCP server, and integrating Oracle AI Agent Memory for persistent shipment state.

- Repository: [Oracle Developers/oracle-ai-developer-hub](https://github.com/oracle-devrel/oracle-ai-developer-hub)
- Tags: how-to-guide
- Published: 2026-05-10

---

**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 `@tool` decorator 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 `OracleAgentMemory` class.
- **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.

```bash
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.

```python
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.

```python
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.

```python
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

```python
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

```python
@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

```python
@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

```python
@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.

```python
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.

```python
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_shipments` and `update_shipment`) to maintain clean separation from LLM orchestration code.
- **MCP abstraction**: Use `create_sdk_mcp_server` to automatically expose tools via the Model Context Protocol without handwritten HTTP handlers.
- **Persistent memory**: The `OracleAgentMemory` class in [`oracleagentmemory/core.py`](https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/oracleagentmemory/core.py) provides 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_DB` with 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.