Skip to content

ZingZing001/mcpsWithCopilot

Repository files navigation

Fuel Data Translation Pipeline

AI-Driven MCP Integration with Microsoft Copilot Studio


Table of Contents

  1. Project Overview
  2. How It Works
  3. Architecture
  4. Prerequisites
  5. Project Structure
  6. Environment Setup
  7. Tailscale Configuration
  8. Docker Configuration
  9. MCP Server Details
  10. Running the Project
  11. Microsoft Copilot Studio Setup
  12. Testing
  13. Demo Guide
  14. Security
  15. Known Limitations
  16. Troubleshooting

1. Project Overview

This project implements an AI-driven fuel data translation pipeline. The core concept is simple:

A customer describes their fuel transaction data in plain English. An AI agent figures out how to map it to the EROAD API format, writes the conversion code, tests it, and deploys it. After that, the conversion runs automatically with no AI involved.

The Key Design Principle

The AI does the hard thinking once. After the translation script is generated and validated, it runs in the Translation Engine with no AI involvement — making it fast, cheap, and deterministic.

AI generates script → deploys to engine → engine runs forever without AI

Why This Matters

Without this system, every new customer data format requires a developer to manually write a translation script. With this system, a user can describe their data in plain language and the AI handles the rest.


2. How It Works

Step by Step Flow

1. User describes their data in plain English to the Copilot agent

2. Agent calls MCP #1 (EROAD) to understand:
   - What fields EROAD requires
   - What format each field must be in
   - What fuel codes are valid

3. Agent calls MCP #2 (Translator) to understand:
   - What conversion functions are available
   - How to handle date formats, timezones, type coercion

4. Agent generates a JavaScript translation script

5. Agent pushes the script to the Translation Engine via MCP #2

6. Agent tests the script with a sample record

7. Agent validates the output against the EROAD schema via MCP #1

8. Agent submits a test transaction to confirm end-to-end flow

9. Done — the script is deployed and runs without AI from this point

Runtime Flow (No AI)

Customer data → Translation Engine → EROAD-compatible payload → EROAD API

3. Architecture

┌─────────────────────────────────────────────────────────┐
│                  Microsoft Copilot Studio                │
│                                                          │
│  ┌─────────────────────────────────────────────────┐    │
│  │              Copilot Agent                       │    │
│  │  Instructions: map customer data to EROAD format │    │
│  └──────────────────┬──────────────────────────────┘    │
│                     │                                    │
│          ┌──────────┴──────────┐                        │
│          ▼                     ▼                        │
│   ┌─────────────┐      ┌──────────────────┐            │
│   │   MCP #1    │      │     MCP #2       │            │
│   │  EROAD API  │      │  Translation     │            │
│   │  (port 8000)│      │  Engine          │            │
│   │             │      │  (port 8001)     │            │
│   └──────┬──────┘      └────────┬─────────┘            │
└──────────┼─────────────────────┼──────────────────────┘
           │                     │
           ▼                     ▼
    ┌─────────────────────────────────┐
    │         Docker Containers       │
    │                                 │
    │  ┌──────────────────────────┐   │
    │  │  ts-mcp-eroad (Tailscale)│   │
    │  │  mcp-eroad (Node.js)     │   │
    │  └──────────────────────────┘   │
    │                                 │
    │  ┌──────────────────────────┐   │
    │  │  ts-mcp-translator       │   │
    │  │  mcp-translator (Node.js)│   │
    │  └──────────────────────────┘   │
    └─────────────────────────────────┘
           │                     │
           ▼                     ▼
    ┌─────────────────────────────────┐
    │         Tailscale Funnel        │
    │                                 │
    │  mcp-eroad.tail11fa6e.ts.net    │
    │  mcp-translator.tail11fa6e.ts.net│
    └─────────────────────────────────┘

4. Prerequisites

Required Software

Software Version Download
Docker Desktop (macOS) Latest https://www.docker.com/products/docker-desktop
Tailscale Latest https://tailscale.com/download/mac
Node.js 18+ https://nodejs.org

Required Accounts

Account Purpose URL
Tailscale VPN and public endpoint exposure https://tailscale.com
Microsoft Copilot Studio access https://copilotstudio.microsoft.com

macOS Specific Notes

On macOS, Docker containers cannot use Linux kernel-level TUN devices. Tailscale must run in userspace mode. This is handled automatically by the TS_USERSPACE=true environment variable in the Docker Compose configuration.

Do NOT add the following to your Docker Compose on macOS:

  • devices: /dev/net/tun — Linux only
  • cap_add: net_admin — Linux only
  • cap_add: sys_module — Linux only

5. Project Structure

fuel-translation-pipeline/
│
├── .env                          # Auth keys and secrets (never commit)
├── .gitignore                    # Git ignore rules
├── docker-compose.yml            # Docker container definitions
├── README.md                     # This file
│
├── mcp-eroad/                    # MCP #1 — EROAD Fuel API Server
│   ├── Dockerfile                # Container build instructions
│   ├── package.json              # Node.js dependencies
│   └── server.js                 # MCP server code
│
├── mcp-translator/               # MCP #2 — Translation Engine Server
│   ├── Dockerfile                # Container build instructions
│   ├── package.json              # Node.js dependencies
│   └── server.js                 # MCP server code
│
├── tailscale-eroad/              # Tailscale config for EROAD MCP
│   ├── state/                    # Tailscale state (never commit)
│   ├── config/
│   │   └── serve.json            # Tailscale serve configuration
│   └── start.sh                  # Auto-funnel startup script
│
└── tailscale-translator/         # Tailscale config for Translator MCP
    ├── state/                    # Tailscale state (never commit)
    ├── config/
    │   └── serve.json            # Tailscale serve configuration
    └── start.sh                  # Auto-funnel startup script

6. Environment Setup

Step 1: Clone the Repository

git clone <your-repo-url>
cd fuel-translation-pipeline

Step 2: Create Your .env File

cp .env.example .env

Edit .env and fill in your values:

# Tailscale Auth Keys
# Generate these at https://login.tailscale.com/admin/settings/keys
# Settings: Reusable=Yes, Ephemeral=No, Pre-approved=Yes
TS_AUTHKEY_EROAD=tskey-auth-your-eroad-key-here
TS_AUTHKEY_TRANSLATOR=tskey-auth-your-translator-key-here

# EROAD API Key
# Leave blank for mock mode
# Fill in when connecting to real EROAD API
# EROAD_API_KEY=your-real-api-key-here

Step 3: Generate Tailscale Auth Keys

  1. Go to https://login.tailscale.com/admin/settings/keys
  2. Click Generate auth key
  3. For the EROAD key:
    • Description: mcp-eroad
    • Reusable: Yes
    • Ephemeral: No
    • Pre-approved: Yes
    • Tags: tag:mcp-eroad
  4. Copy the key and paste into .env
  5. Repeat for the translator key with tag tag:mcp-translator

⚠️ You only see the key once. Copy it immediately.

⚠️ Pre-approved must be ticked otherwise containers will not join the tailnet automatically.


7. Tailscale Configuration

Access Control Policy

Go to https://login.tailscale.com/admin/acls and apply this policy:

{
  "tagOwners": {
    "tag:mcp-eroad":      ["autogroup:admin"],
    "tag:mcp-translator": ["autogroup:admin"],
    "tag:mcp-host":       ["autogroup:admin"]
  },

  "grants": [
    {
      "src": ["tag:mcp-host"],
      "dst": ["tag:mcp-eroad"],
      "ip":  ["tcp:8000", "tcp:443"]
    },
    {
      "src": ["tag:mcp-host"],
      "dst": ["tag:mcp-translator"],
      "ip":  ["tcp:8001", "tcp:443"]
    },
    {
      "src": ["autogroup:admin"],
      "dst": ["tag:mcp-eroad", "tag:mcp-translator"],
      "ip":  ["*"]
    },
    {
      "src": ["tag:mcp-eroad"],
      "dst": ["tag:mcp-translator"],
      "ip":  ["tcp:8001", "tcp:443"]
    },
    {
      "src": ["tag:mcp-translator"],
      "dst": ["tag:mcp-eroad"],
      "ip":  ["tcp:8000", "tcp:443"]
    },
    {
      "src": ["autogroup:member"],
      "dst": ["tag:mcp-eroad", "tag:mcp-translator"],
      "ip":  ["*"]
    }
  ],

  "ssh": [
    {
      "action": "check",
      "src":    ["autogroup:admin"],
      "dst":    ["tag:mcp-eroad", "tag:mcp-translator"],
      "users":  ["autogroup:nonroot", "root"]
    }
  ],

  "nodeAttrs": [
    {
      "target": ["tag:mcp-eroad", "tag:mcp-translator"],
      "attr":   ["funnel"]
    }
  ],

  "tests": [
    {
      "src":    "tag:mcp-host",
      "accept": ["tag:mcp-eroad:8000"]
    },
    {
      "src":    "tag:mcp-host",
      "accept": ["tag:mcp-eroad:443"]
    },
    {
      "src":    "tag:mcp-host",
      "accept": ["tag:mcp-translator:8001"]
    },
    {
      "src":    "tag:mcp-host",
      "accept": ["tag:mcp-translator:443"]
    },
    {
      "src":  "tag:mcp-eroad",
      "deny": ["tag:mcp-host:8000"]
    },
    {
      "src":  "tag:mcp-eroad",
      "deny": ["tag:mcp-host:8001"]
    }
  ]
}

Tag Your Host Mac

sudo tailscale login --advertise-tags=tag:mcp-host

Enable MagicDNS and HTTPS

  1. Go to https://login.tailscale.com/admin/dns
  2. Enable MagicDNS
  3. Enable HTTPS

8. Docker Configuration

docker-compose.yml

version: "3.7"
services:

  # ── Tailscale sidecar for MCP #1 (EROAD) ───────────────────────────────
  ts-mcp-eroad:
    image: tailscale/tailscale:latest
    hostname: mcp-eroad
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY_EROAD}
      - TS_EXTRA_ARGS=--advertise-tags=tag:mcp-eroad
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=true
    volumes:
      - ${PWD}/tailscale-eroad/state:/var/lib/tailscale
      - ${PWD}/tailscale-eroad/start.sh:/start.sh
    entrypoint: >
      /bin/sh -c "tailscaled --tun=userspace-networking &
      sleep 5 && sh /start.sh & wait"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "tailscale", "status"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

  # ── MCP #1 — EROAD API ─────────────────────────────────────────────────
  mcp-eroad:
    build: ./mcp-eroad
    network_mode: service:ts-mcp-eroad
    depends_on:
      ts-mcp-eroad:
        condition: service_healthy
    restart: unless-stopped

  # ── Tailscale sidecar for MCP #2 (Translator) ──────────────────────────
  ts-mcp-translator:
    image: tailscale/tailscale:latest
    hostname: mcp-translator
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY_TRANSLATOR}
      - TS_EXTRA_ARGS=--advertise-tags=tag:mcp-translator
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=true
    volumes:
      - ${PWD}/tailscale-translator/state:/var/lib/tailscale
      - ${PWD}/tailscale-translator/start.sh:/start.sh
    entrypoint: >
      /bin/sh -c "tailscaled --tun=userspace-networking &
      sleep 5 && sh /start.sh & wait"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "tailscale", "status"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

  # ── MCP #2 — Translation Engine ────────────────────────────────────────
  mcp-translator:
    build: ./mcp-translator
    network_mode: service:ts-mcp-translator
    depends_on:
      ts-mcp-translator:
        condition: service_healthy
    restart: unless-stopped

Dockerfiles

Both MCP servers use the same Dockerfile pattern:

mcp-eroad/Dockerfile

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8000
CMD ["node", "server.js"]

mcp-translator/Dockerfile

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8001
CMD ["node", "server.js"]

package.json

Both servers use the same dependencies:

{
  "name": "mcp-server",
  "version": "1.0.0",
  "main": "server.js",
  "dependencies": {
    "express": "^4.18.2"
  }
}

9. MCP Server Details

MCP #1 — EROAD Fuel API (Port 8000)

This server gives the AI agent awareness of the EROAD target schema. It runs in mock mode — no real EROAD API calls are made. Transactions are stored in memory.

REST Endpoints

Method Endpoint Description
GET /health Server health check
GET /schema EROAD field definitions and constraints
GET /fuelcodes Valid fuel product codes
POST /validate Validate a payload against EROAD schema
POST /fuel Submit a mock fuel transaction
GET /fuel/transactions View all stored mock transactions
GET /fuel/transactions/:id View a single transaction
DELETE /fuel/transactions Clear all mock transactions

MCP Protocol Endpoints

Method Endpoint Description
GET /mcp Server discovery
POST /mcp MCP Streamable HTTP protocol handler
GET /.well-known/mcp Well-known discovery

MCP Tools Exposed to Copilot

Tool Description
get_schema Get EROAD field definitions
get_fuel_codes Get valid product codes
validate_payload Validate a transaction payload
submit_transaction Submit to mock EROAD API
get_transactions View stored transactions

EROAD Schema Requirements

Field Type Rules
registrationPlate string Uppercase, no spaces, e.g. ABC123
registrationState string Default NZ
transactionTimestamp datetime UTC ISO 8601
amount decimal Total including GST
transactionType string PURCHASE or VOID
fuelProducts array At least one entry required
fuelProducts[].productCode string DIESEL, PETROL_REGULAR, PETROL_PREMIUM
fuelProducts[].quantity decimal Volume in litres
fuelProducts[].amount decimal Cost of product

MCP #2 — Translation Engine (Port 8001)

This server accepts translation scripts from the AI agent, stores them, and executes them against incoming source data.

REST Endpoints

Method Endpoint Description
GET /health Server health check
GET /patterns Available conversion patterns
POST /translation/push Store a new translation script
POST /translation/test Test the current script
GET /translation/status Get current active script
POST /translation/execute Execute script on real data

MCP Protocol Endpoints

Method Endpoint Description
GET /mcp Server discovery
POST /mcp MCP Streamable HTTP protocol handler
GET /.well-known/mcp Well-known discovery

MCP Tools Exposed to Copilot

Tool Description
get_patterns Get available conversion patterns
push_script Push a translation script
test_translation Test with a sample record
get_status Get active script
execute_translation Run on real data

Available Conversion Patterns

Pattern Description Example
toUpperCase Convert to uppercase input.toUpperCase()
stripSpaces Remove whitespace input.replace(/\s/g, '')
formatPlate Format NZ plate input.toUpperCase().replace(/\s/g, '')
parseDecimal String to decimal parseFloat(input)
removeGST Remove NZ GST 15% parseFloat(input) / 1.15
nztToUtc NZT to UTC See server.js for full implementation

10. Running the Project

Start

cd fuel-translation-pipeline
docker compose up -d

Wait about 30 seconds for Tailscale to authenticate and the servers to start. The startup scripts automatically configure Tailscale Funnel.

Verify Everything Is Running

# Check container logs
docker compose logs --tail=10

# Check EROAD MCP is responding
curl https://mcp-eroad.tail11fa6e.ts.net/health

# Check Translator MCP is responding
curl https://mcp-translator.tail11fa6e.ts.net/health

# Check funnel status
docker exec -it fuel-translation-pipeline-ts-mcp-eroad-1 \
  tailscale funnel status

docker exec -it fuel-translation-pipeline-ts-mcp-translator-1 \
  tailscale funnel status

Stop

docker compose down

Rebuild After Code Changes

docker compose down
docker compose build --no-cache
docker compose up -d

Quick Reference Cheat Sheet

# START
cd fuel-translation-pipeline && docker compose up -d

# VERIFY
curl https://mcp-eroad.tail11fa6e.ts.net/health
curl https://mcp-translator.tail11fa6e.ts.net/health

# STOP
docker compose down

# REBUILD
docker compose down && docker compose build --no-cache && docker compose up -d

# CLEAR DEMO DATA
curl -X DELETE https://mcp-eroad.tail11fa6e.ts.net/fuel/transactions

# VIEW LOGS
docker compose logs -f

11. Microsoft Copilot Studio Setup

Prerequisites

  • Microsoft work account with Copilot Studio access
  • Both MCP servers running and publicly accessible
  • Generative Orchestration enabled in your agent

Step 1: Create an Agent

  1. Go to https://copilotstudio.microsoft.com
  2. Click CreateNew agent
  3. Name it Fuel Translation Agent
  4. Click Create

Step 2: Enable Generative Mode

  1. Click SettingsGenerative AI
  2. Set Generative mode to On
  3. Click Save

Step 3: Connect MCP #1

  1. Go to ToolsAdd a toolNew tool
  2. Select Model Context Protocol
  3. Enter URL:
https://mcp-eroad.tail11fa6e.ts.net/mcp
  1. Click Next — tools will be discovered automatically
  2. Click Save

Step 4: Connect MCP #2

Repeat the same process with:

https://mcp-translator.tail11fa6e.ts.net/mcp

Step 5: Add Agent Instructions

In the agent Instructions field paste:

You are a fuel data translation assistant for EROAD.

When a user describes their source data model you will:

1. Use the EROAD MCP to understand the target schema
   - Call get_schema to get field definitions
   - Call get_fuel_codes to get valid fuel product codes

2. Use the Translation MCP to get available conversion patterns
   - Call get_patterns to see what conversions are available

3. Generate a JavaScript translation function called translate(input)
   that maps the user fields to the EROAD schema handling all
   conversions including:
   - Registration plate to uppercase with no spaces
   - NZT datetime to UTC
   - String amounts to decimals
   - GST inclusive amounts

4. Push the script via push_script

5. Test the script via test_translation with a sample record

6. Validate the output via validate_payload on the EROAD MCP

7. If validation passes submit a test transaction via submit_transaction

8. Show the user the transaction ID and confirm success

Always ensure the final output has:
- registrationPlate uppercase with no spaces
- transactionTimestamp in UTC ISO 8601
- amount as a decimal number
- transactionType as PURCHASE or VOID
- fuelProducts array with at least one entry

MCP Protocol Details

This server uses Streamable HTTP transport as required by Copilot Studio. The protocol version is 2024-11-05.

The key header that Copilot Studio looks for is:

x-ms-agentic-protocol: mcp-streamable-1.0

This is set automatically on all /mcp responses.


12. Testing

Test the MCP Protocol Directly

# Test initialize
curl -X POST https://mcp-eroad.tail11fa6e.ts.net/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": {"name": "test", "version": "1.0"}
    }
  }'

# Test tools list
curl -X POST https://mcp-eroad.tail11fa6e.ts.net/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

# Test tool call
curl -X POST https://mcp-eroad.tail11fa6e.ts.net/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "get_schema",
      "arguments": {}
    }
  }'

Test the Translation Engine

# Push a test script
curl -X POST https://mcp-translator.tail11fa6e.ts.net/translation/push \
  -H "Content-Type: application/json" \
  -d '{
    "script": "function translate(input) { return { registrationPlate: input.reg.toUpperCase().replace(/\\s/g, \"\"), registrationState: \"NZ\", transactionTimestamp: new Date(new Date(input.purchase_time).getTime() - 13*60*60*1000).toISOString(), amount: parseFloat(input.amount), transactionType: \"PURCHASE\", fuelProducts: [{ productCode: \"DIESEL\", quantity: parseFloat(input.litres), amount: parseFloat(input.amount) }] }; }"
  }'

# Test the script
curl -X POST https://mcp-translator.tail11fa6e.ts.net/translation/test \
  -H "Content-Type: application/json" \
  -d '{
    "reg": "abc 123",
    "purchase_time": "2026-03-19T10:34:10",
    "amount": "115.00",
    "litres": "85.5"
  }'

# Execute against real data (no AI)
curl -X POST https://mcp-translator.tail11fa6e.ts.net/translation/execute \
  -H "Content-Type: application/json" \
  -d '{
    "reg": "xyz 789",
    "purchase_time": "2026-03-19T14:22:00",
    "amount": "200.00",
    "litres": "150.0"
  }'

Test the Mock EROAD API

# Submit a transaction
curl -X POST https://mcp-eroad.tail11fa6e.ts.net/fuel \
  -H "Content-Type: application/json" \
  -d '{
    "registrationPlate": "ABC123",
    "registrationState": "NZ",
    "transactionTimestamp": "2026-03-18T21:34:10.000Z",
    "amount": 115.00,
    "transactionType": "PURCHASE",
    "fuelProducts": [{
      "productCode": "DIESEL",
      "quantity": 85.5,
      "amount": 115.00
    }]
  }'

# View stored transactions
curl https://mcp-eroad.tail11fa6e.ts.net/fuel/transactions

# Clear transactions
curl -X DELETE https://mcp-eroad.tail11fa6e.ts.net/fuel/transactions

13. Demo Guide

Before the Demo

# Start everything
docker compose up -d

# Wait 30 seconds then verify
curl https://mcp-eroad.tail11fa6e.ts.net/health
curl https://mcp-translator.tail11fa6e.ts.net/health

# Clear any previous demo data
curl -X DELETE https://mcp-eroad.tail11fa6e.ts.net/fuel/transactions

The Demo Prompt

Paste this into your Copilot agent:

I have fuel transaction data I need to load into EROAD.
My data has the following fields:

- "reg" — an NZTA vehicle registration plate,
  for example "abc 123"

- "purchase_time" — the date and time the fuel was
  purchased in New Zealand time, could be in either
  ISO format like "2026-03-19T10:34:10" or human
  readable like "Mar 19th 2026 10:34:10 am"

- "amount" — the total dollar amount paid at the pump
  including GST, for example "115.00"

- "litres" — the volume of fuel purchased in litres,
  for example "85.5"

- "fuel_type" — always "diesel"

Please create a translation mapping for my data and
test it with this sample record:

{
  "reg": "abc 123",
  "purchase_time": "2026-03-19T10:34:10",
  "amount": "115.00",
  "litres": "85.5",
  "fuel_type": "diesel"
}

What to Watch For

✅ Agent calls get_schema from EROAD MCP
✅ Agent calls get_patterns from Translator MCP
✅ Agent generates a translate(input) function
✅ Agent calls push_script to deploy it
✅ Agent calls test_translation with the sample record
✅ Agent calls validate_payload to verify the output
✅ Agent calls submit_transaction and gets a MOCK ID
✅ Transaction appears in /fuel/transactions

After the Demo — Show the Transaction

curl https://mcp-eroad.tail11fa6e.ts.net/fuel/transactions

Show that the mock EROAD database contains exactly what would have been sent to the real EROAD API.

Show the Translation Runs Without AI

curl -X POST \
  https://mcp-translator.tail11fa6e.ts.net/translation/execute \
  -H "Content-Type: application/json" \
  -d '{
    "reg": "DEF 456",
    "purchase_time": "2026-03-19T14:22:00",
    "amount": "200.00",
    "litres": "150.0",
    "fuel_type": "diesel"
  }'

This runs the translation script directly with no AI involved — demonstrating the key design principle.


14. Security

Current Security Model (Experimental)

Layer Mechanism Notes
Network Tailscale VPN Peer-to-peer encrypted
Public exposure Tailscale Funnel HTTPS only via Let's Encrypt
ACL Tailscale ACL Restricts which nodes can communicate
Application IP allowlist middleware Blocks non-Microsoft, non-Tailscale IPs
Data Mock mode only No real credentials or customer data

What Is and Is Not Protected

Protected Not Protected
App ports 8000 and 8001 from public MCP endpoints accessible via HTTPS
Direct container access No auth on MCP endpoints themselves
Non-Tailscale traffic to containers Translation scripts stored in memory only

IP Allowlist

The Node.js servers include middleware that restricts access to:

  • Localhost (127.0.0.1)
  • Tailscale network (100.x.x.x range)
  • Tailscale IPv6 (fd7a:115c:a1e0 range)
  • Microsoft Azure IP ranges used by Copilot Studio

Production Considerations

⚠️ This setup is for experimental and demo purposes only.

For production the following changes are recommended:

  1. Move off Tailscale Funnel to Azure networking
  2. Add API key authentication to MCP endpoints
  3. Add persistent storage for translation scripts
  4. Add rate limiting to prevent abuse
  5. Add request logging and monitoring
  6. Connect to the real EROAD API with proper key management
  7. Use Azure Key Vault for secret storage

15. Known Limitations

Limitation Impact Recommended Fix
Tailscale Funnel not production grade Cannot use for real customers Migrate to Azure networking
Translation scripts stored in memory Lost on container restart Add database persistence
No authentication on MCP endpoints Anyone with URL can call them Add API key validation
Funnel config lost on restart Startup script re-creates it Working as designed
Mock EROAD API only Cannot submit real transactions Add real EROAD API key
macOS only tested Unknown behaviour on other OS Test on Linux/Windows

16. Troubleshooting

Containers Not Starting

# Check Docker Desktop is running
docker info

# Check container status
docker compose ps

# View all logs
docker compose logs

Tailscale Auth Failing

# Check auth key is valid
docker compose logs ts-mcp-eroad | grep "invalid key"

# Fix: regenerate auth keys at
# https://login.tailscale.com/admin/settings/keys
# Make sure Pre-approved is ticked

Funnel Not Active After Restart

# Check funnel status
docker exec -it fuel-translation-pipeline-ts-mcp-eroad-1 \
  tailscale funnel status

# If not active, set it manually
docker exec -it fuel-translation-pipeline-ts-mcp-eroad-1 \
  tailscale funnel --bg 8000

docker exec -it fuel-translation-pipeline-ts-mcp-translator-1 \
  tailscale funnel --bg 8001

Curl Returns Connection Refused

# Check the MCP server is actually running
docker compose logs mcp-eroad | tail -5
docker compose logs mcp-translator | tail -5

# Should see:
# MCP #1 EROAD server running on port 8000
# MCP #2 Translation Engine running on port 8001

Copilot Studio Cannot Connect to MCP

  1. Verify the MCP endpoint responds correctly:
curl -X POST https://mcp-eroad.tail11fa6e.ts.net/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
  1. Check Generative mode is enabled in Copilot Studio
  2. Try removing and re-adding the MCP tool
  3. Check the URL has no trailing slash issues

Translation Script Errors

# Check what script is currently active
curl https://mcp-translator.tail11fa6e.ts.net/translation/status

# Clear and let the agent regenerate
# (restart the container to clear in-memory script)
docker compose restart mcp-translator

Drop Logs in Tailscale

Drop: TCP{...} no rules matched

This is normal — it is Tailscale internal peer-to-peer traffic. If your curl commands are working, ignore these logs. They do not indicate a problem with the Funnel setup.


Appendix A: Public Endpoints

Service URL
EROAD MCP https://mcp-eroad.tail11fa6e.ts.net
Translation MCP https://mcp-translator.tail11fa6e.ts.net
EROAD Health https://mcp-eroad.tail11fa6e.ts.net/health
Translator Health https://mcp-translator.tail11fa6e.ts.net/health
Mock Transactions https://mcp-eroad.tail11fa6e.ts.net/fuel/transactions

Appendix B: MCP Protocol Reference

Both servers implement the MCP protocol version 2024-11-05 using Streamable HTTP transport as required by Copilot Studio.

Supported methods:

  • initialize
  • notifications/initialized
  • tools/list
  • tools/call

Required response header:

x-ms-agentic-protocol: mcp-streamable-1.0

Documentation prepared by Johnson Zhang, March 2026
Based on mentoring sessions with Sean Ogden
EROAD Fuel Integration — Experimental Phase

About

AI-driven fuel data translation pipeline using Model Context Protocol (MCP), Microsoft Copilot Studio, Docker, and Tailscale. Converts customer fuel transaction data into COMPANY APPROVED API format using a Copilot agent that generates, validates, and deploys translation scripts.

Topics

Resources

Stars

Watchers

Forks

Contributors