oic mcp as tools provider

The Problem with “AI + Enterprise Systems”

Every enterprise already has a nervous system: integration platforms that connect ERP, CRM, supply chain, finance, and dozens of other systems. Years of business logic are baked into these platforms — workflow rules, data transformations, approval hierarchies, and compliance checks.

When organizations add AI agents on top of this landscape, the typical approach is to rebuild that logic in agent code. Prompt templates, hardcoded API calls, and custom orchestration scripts. The integration platform becomes a bystander.

In this post, we walk through how we used Oracle Integration (OIC) as an MCP tools provider — turning existing enterprise integrations into tools that an AI agent can discover and call autonomously. The agent is built using the OCI Generative AI Python SDK directly (no LangChain, no third-party agent frameworks), paired with three native OCI tools and four OIC MCP tools. The result is a fully automated invoice validation pipeline where the AI reasons through a multi-step workflow, calls the right tools in the right order, and produces auditable decisions.


What Is MCP and Why Does It Matter Here?

Model Context Protocol (MCP) is an open standard for exposing tools and resources to AI models. An MCP server publishes a catalog of callable tools with names, descriptions, and JSON schemas. An MCP client — the AI agent — calls tools/list at startup to discover what’s available, then invokes tools via tools/call using JSON-RPC 2.0. The LLM never sees implementation code; it just sees tool descriptions in natural language and decides which to call.

OIC’s MCP server capability transforms every integration project into a tool catalog. Any integration flow you publish as an MCP action becomes instantly available to any MCP-compatible AI agent — with OAuth2 authentication, session management, and audit logging handled by OIC.

Key architectural insight: The enterprise business logic stays in the integration platform, and the AI agent becomes a pure reasoning and orchestration layer.


Solution Architecture

The system processes invoices end-to-end with no human touchpoints for compliant documents. The workflow is split across two tiers: native OCI tools (steps 1–3) handle discovery, classification, and staging; OIC MCP tools (steps 4a–4d) own the enterprise processing pipeline.

solution architecture

Components at a Glance

StepComponentTypePurpose
xai.grok-4 LLMOCI Generative AIReasoning, tool selection, decision-making
1list_object_storage_documentsNative OCIList invoice documents in the bucket
2classify_document_aiNative OCIConfirm document is an INVOICE; skip all others
3send_object_to_oic_sftpNative OCIStage invoice on OIC SFTP landing zone — handoff to OIC
4adocument_extractOIC MCP ToolExtract vendor, amount, line items, payment terms through OCI Doc Understanding
4brisk_assessmentOIC MCP Tool, Decision
Service
Vendor risk scoring, approval routing, and payment optimization
4ccontract_searchOIC MCP Tool · ATP Vector DBVector search of vendor contracts for standard payment terms
4derp_invoice_createOIC MCP Tool · ERP CloudWrite validated invoice record to Oracle Fusion

Building the MCP Client

The MCPClient class handles all communication with the OIC MCP server using JSON-RPC 2.0 — no external MCP SDK is required.

Session Initialization

Python Code

class MCPClient:
    def initialize(self) -> dict:
        params = {
            "protocolVersion": "2024-11-05",
            "capabilities": {"roots": {"listChanged": True}, "sampling": {}},
            "clientInfo": {"name": "Smart Invoice Validation MCP Client", "version": "1.0.0"}
        }
        response = self._send_request("initialize", params)
        result   = self._extract_result(response)
        # OIC returns Mcp-Session-Id in the response header —
        # included in all subsequent requests for session correlation
        self._initialized = True
        self._send_initialized_notification()
        return result

Tool Discovery

Python Code

def list_tools(self) -> list:
    response = self._send_request("tools/list")
    result   = self._extract_result(response)
    return result.get("tools", []) if result else []

This single call returns all four OIC tools — document_extractrisk_assessmentcontract_searcherp_invoice_create — as JSON schema objects. The agent doesn’t need to know these tools exist at code-write time. They are discovered at runtime.

Authentication: OAuth2 Client Credentials

Every request to OIC includes a Bearer token obtained via OAuth2 client credentials flow. The OAuth2TokenManager handles token lifecycle automatically — fetching a new token when the current one is within 60 seconds of expiry:

Python Code

def get_token(self) -> str:
    # Return cached token if still valid (with 60s buffer)
    if self._access_token and time.time() < (self._token_expiry - 60):
        return self._access_token
    token_data         = self._fetch_token()
    self._access_token  = token_data["access_token"]
    self._token_expiry  = time.time() + int(token_data.get("expires_in", 3600))
    return self._access_token

Building the Native OCI Tools

Three OCI-native capabilities are implemented as local Python tools running directly against OCI services, with no OIC involvement. These cover steps 1–3: discovering files, classifying them, and staging the confirmed invoice for OIC to process.

Step 1: List Invoice Documents from OCI Object Storage

The agent’s first action is to discover what documents are available for processing. list_object_storage_documents calls the OCI Object Storage API to enumerate all objects in the configured bucket and returns their names as a structured list. The LLM uses this output to decide which files require classification.

Python Code

def list_object_storage_documents(bucket_name: str):
    """List objects from an OCI Object Storage bucket."""
    response = object_storage_client.list_objects(
        namespace_name=NAMESPACE,
        bucket_name=bucket_name
    )
    objects = [
        {"name": obj.name}
        for obj in response.data.objects
    ]
    return {"objects": objects}

The client is initialized once at module level using oci.config.from_file(), which reads the default OCI CLI profile from ~/.oci/config. The returned list of object names feeds directly into the next tool call — the agent iterates over each document and classifies it individually.

Step 2: Document Classification with OCI Document Understanding

Python Code

def classify_document_ai(bucket_name: str, object_name: str):
    # Download from Object Storage
    obj     = object_client.get_object(NAMESPACE, bucket_name, object_name)
    encoded = base64.b64encode(obj.data.content).decode()

    response = ai_document_client.analyze_document(
        analyze_document_details=oci.ai_document.models.AnalyzeDocumentDetails(
            compartment_id=COMPARTMENT_ID,
            document=oci.ai_document.models.InlineDocumentDetails(
                source="INLINE", data=encoded
            ),
            features=[oci.ai_document.models.DocumentClassificationFeature(
                feature_type="DOCUMENT_CLASSIFICATION"
            )]
        )
    )
    doc = response.data.detected_document_types[0]
    return {"file": object_name, "document_type": doc.document_type, "confidence": doc.confidence}

The INLINE source mode sends document bytes directly in the request rather than referencing a storage URI. The agent’s system prompt enforces a minimum confidence threshold of 0.8 — documents below that are not processed automatically.

Step 3: SFTP Transfer — Handing Off to OIC

Before the OIC MCP tools are called, the invoice must be staged where OIC can access it. The agent pushes the file to OIC’s built-in SFTP landing zone using paramiko. This is the handoff point: once the file lands, OIC’s document_extract MCP tool reads and processes it.

Python Code

def send_object_to_oic_sftp(bucket_name: str, object_name: str):
    # Download from Object Storage to /tmp
    response   = object_storage_client.get_object(NAMESPACE, bucket_name, object_name)
    local_path = f"/tmp/{object_name}"
    with open(local_path, "wb") as f:
        for chunk in response.data.raw.stream(1024 * 1024, decode_content=False):
            f.write(chunk)

    # Upload to OIC SFTP landing zone
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname=OIC_SFTP_HOST, username=OIC_SFTP_USER,
               port=OIC_SFTP_PORT, password=PASSWORD)
    sftp = ssh.open_sftp()
    sftp.put(local_path, f"{REMOTE_DIR}/{object_name}")
    sftp.close(); ssh.close(); os.remove(local_path)

OIC MCP Tools

Once the invoice is staged on the OIC SFTP landing zone, the agent delegates all enterprise processing to OIC through the Model Context Protocol. OIC exposes four integration flows as MCP tools — each handling a distinct step of the invoice lifecycle. The agent discovers these tools at startup through tools/list and calls them by name through tools/call, just like any other tool in the unified registry.

oic mcp tools

Step 4: Extract Invoice Data (document_extract)

The first OIC MCP tool reads the staged file from the SFTP server and uses OCI AI Document Understanding to extract structured invoice fields — vendor name, line items, totals, and more — from the raw PDF.

  • Receives PDF documents through the OIC SFTP Server adapter and downloads from the FTP landing zone
  • Leverages AI-powered document analysis to automatically extract invoice details (header, line items, and totals)
  • Returns structured JSON payload consumed by downstream OIC tools
  • Includes a catch-all fault handler block for reliability against malformed or unreadable documents

Why OIC here, not a local tool? OIC’s SFTP adapter and OCI Document Understanding orchestration are already built and certified. Calling this through MCP reuses existing enterprise integration logic without duplicating it in Python.

extract invoice data

Step 5: Risk Assessment and Payment Terms (risk_assessment)

This tool orchestrates payment terms derivation and approval routing. It triggers an OIC REST endpoint that internally invokes an OIC Decision Service to evaluate the invoice against business rules.

  • Invokes an OIC Decision Service to derive and calculate payment terms and assess invoice risk
  • Routes the outcome — low risk, medium risk, or requires manual review — back as a structured response
  • Includes a fault handler for error management during Decision Service execution
  • Sends the final response back to the triggering REST endpoint for the agent to consume

Deterministic Rules implemented in OIC Decision Model

risk assessment decision model
risk assement tool

Step 6: Contract Search Against Oracle ATP (contract_search)

Enables semantic contract retrieval using Oracle Autonomous Transaction Processing (ATP) with a vector search. Given a vendor name or contract identifier, this tool searches the ATP database for matching contract terms and returns the vendor’s standard payment conditions.

  • Receives search requests through a REST endpoint and processes them through Oracle ATP vector search
  • Matches vendor invoices against contract terms stored as embeddings in the ATP vector store
  • Returns contract search results including standard payment terms, discount windows, and penalties
  • Implements a fault handler for managing ATP service failures and API errors

Vector Search in Oracle ATP: Contract documents are prechunked and embedded into the ATP vector store. At runtime, contract_search performs a semantic similarity query — retrieving the most relevant contract clauses even when the vendor name or invoice wording doesn’t match exactly.

vector search tool

Step 7: Validate and Create Invoice in ERP Cloud (erp_invoice_create)

The final OIC MCP tool completes the invoice lifecycle by validating business context and creating the invoice record in Oracle ERP Cloud (Oracle Fusion).

  • Validates business unit and supplier information before committing the invoice to ERP Cloud
  • Routes execution through multiple conditional paths based on validation results
  • Creates the invoice in ERP Cloud with a default supplier site assignment when no explicit site is provided
  • Includes activity stream logging throughout and a comprehensive catch-all error handler
validate and create invoice tool

Together, these four OIC MCP tools form a complete enterprise processing pipeline — invoked sequentially by the agent as tool calls, with each tool’s output feeding the next decision in the agentic loop.


Building the Agent with OCI Generative AI SDK

The MCPAgent class uses the OCI Generative AI SDK directly — no agent frameworks and no abstraction layers. Local tools and MCP tools are merged into one flat registry before being passed to the LLM.

Merging Tool Registries

Python Code

class MCPAgent:
    def __init__(self):
        self.client   = oci.generative_ai_inference.GenerativeAiInferenceClient(config)
        self.model_id = "xai.grok-4"

        # Discover OIC tools via MCP at startup
        self.mcp       = MCPClient()
        self.mcp.initialize()
        self.mcp_tools = self.mcp.list_tools()  # [{name, description, inputSchema}, …]

        # Local OCI tools with JSON schemas
        self.local_tools       = {"list_object_storage_documents": list_object_storage_documents, …}
        self.local_tool_schemas = [{"name": "…", "inputSchema": {…}}, …]

        # Single unified registry — LLM sees one flat list
        self.tools = self.mcp_tools + self.local_tool_schemas

The Agentic Loop

Python Code

def run(self, user_input: str) -> str:
    self.conversation.append({"role": "user", "content": user_input})

    while True:
        message = self._call_llm()

        if message.tool_calls:
            for tool_call in message.tool_calls:
                tool_name  = tool_call.name
                arguments = json.loads(tool_call.arguments)

                # Route: local function or OIC MCP
                if tool_name in self.local_tools:
                    tool_result = self.local_tools[tool_name](**arguments)
                else:
                    tool_result = self.mcp.call_tool(tool_name, arguments)

                self.conversation.append({"role": "assistant", "tool_calls": […]})
                self.conversation.append({"role": "tool", "content": json.dumps(tool_result), …})
            continue  # Send results back to LLM

        else:
            final_text = message.content[0].text
            self.conversation.append({"role": "assistant", "content": final_text})
            return final_text

The LLM sees the full conversation history at every turn. This is how it knows risk_assessment completed before attempting contract_search — the state machine is implicit in the history, not in Python code.


Encoding Business Rules in the System Prompt

Business logic lives in the system prompt, not in Python code. This makes it auditable by business analysts and modifiable without a code deployment:

System Prompt

1. Classify the document. If not an INVOICE, stop.
2. Use document_extract tool to get structured invoice data.
3. Run risk_assessment FIRST with extracted data.
4. ONLY AFTER risk_assessment: run contract_search to get
   vendor standard payment terms from the ATP vector database.
5. Validate payment terms:
   - MATCH  → proceed to erp_invoice_create
   - MISMATCH → STOP and generate discrepancy report
   
EXECUTION ORDER — MANDATORY:
document_extract → risk_assessment → contract_search → erp_invoice_create

STOP CONDITIONS:
- Payment terms mismatch between invoice and contract
- Vendor contract not found in ATP vector store
- Document classification confidence below 0.8

The Complete Invoice Processing Workflow

Here is what actually happens when a user submits queries in multiple turns:

“list files in invoice-bucket”

“classify files”

“send Invoice.pdf to sftp”

“process vendor invoice file: Invoice.pdf dir: /home/user/invoice-processing

solution workflow

What Happens When Validation Fails

The system prompt’s STOP CONDITIONS ensure the agent never silently skips a failed check. For a payment terms mismatch, the agent generates a structured report and does not call erp_invoice_create:

when validation fails

Key Design Patterns Worth Reusing

OIC’s role in this architecture goes beyond a single use case. These patterns reflect a broader principle: OIC is the right tools backend for enterprise AI agents, regardless of which agent framework or model is used.

key design patterns

Why This Architecture Wins

The combination of OCI Generative AI, OCI Document Understanding, and OIC as an MCP tools provider delivers something that neither AI nor integration platforms can achieve alone:

LayerContribution
OCI Generative AIMulti-step reasoning, natural language understanding, and adaptive decision-making
Oracle IntegrationEnterprise integrations, security, audit logging, workflow governance, and certified business logic
Oracle ATP + Vector SearchSemantic retrieval of contract terms — no hardcoded lookup tables
Model Context ProtocolThe bridge that lets AI and OIC work together without either compromising its strengths

As MCP adoption grows across enterprise software, the pattern demonstrated here — integration platform as MCP server, AI SDK as reasoning layer — will become a standard architecture for agentic automation. This invoice validation system is working proof of that pattern today.


Acknowledgements

A huge thank you to the SE Hub team for their valuable contributions in building the OCI Gen AI artifacts. Special appreciation to Venkata Nagakiran Eluri and Lohith R for their dedication and efforts. Your work has been instrumental in making this possible.

Learn More

OCI Python SDK Reference

OCI Generative AI Service Documentation

OCI Document Understanding

Oracle Integration Cloud — MCP Server

Model Context Protocol Specification