Skip to content

Examples

github-actions[bot] edited this page Mar 23, 2026 · 20 revisions

Examples

This page contains practical, real-world usage examples for the matlab-mcp-server. Each example progresses from simple to advanced scenarios, with complete runnable code blocks.

Architecture Overview

Understanding how code flows through the system helps you use it effectively:

graph LR
    A["AI Agent<br/>(Claude, etc)"] -->|MCP Protocol| B["FastMCP Server"]
    B -->|Routes Tool Call| C["Job Executor"]
    C -->|Acquire Engine| D["Engine Pool<br/>(elastic scaling)"]
    D -->|Execute Code| E["MATLAB Engine"]
    E -->|Results| D
    D -->|Release| C
    C -->|Format Output| F["Result Formatter"]
    F -->|Plotly Conversion| G["Interactive Plots"]
    F -->|Text/Files| H["Inline Response"]
    G -->|MCP Response| A
    H -->|MCP Response| A
Loading

Key observations:

  • Session isolation: Each connection gets its own MATLAB workspace
  • Automatic promotion: Fast code (< 30s) returns inline; slow code becomes async jobs
  • Interactive plots: MATLAB figures are auto-converted to Plotly JSON
  • File management: Upload, execute, retrieve results within session temp directory

Basic Usage Examples

Example 1: Simple Calculation

What it demonstrates: Basic arithmetic, MATLAB output capture

Prerequisites: None — this runs in any MATLAB environment

% Calculate the sum of squares from 1 to 100
n = 100;
result = sum(1:n).^2;
fprintf('Sum of 1²+2²+...+%d² = %d\n', n, result);

Agent prompt: "Calculate the sum of squares from 1 to 100"

Expected output:

Sum of 1²+2²+...+100² = 338350

Example 2: Matrix Eigenvalues

What it demonstrates: Linear algebra, eigenvalue decomposition

Prerequisites: Core MATLAB (no toolboxes required)

% Find eigenvalues and eigenvectors of a magic square
A = magic(4);
[V, D] = eig(A);

disp('Magic 4x4 Matrix:');
disp(A);
disp('Eigenvalues:');
disp(diag(D));

Agent prompt: "Create a 4x4 magic square and compute its eigenvalues"

Expected output:

Magic 4x4 Matrix:
    16     2     3    13
     5    11    10     8
     9     7     6    12
     4    14    15     1

Eigenvalues:
   34.0000
    8.9443
    0.0000
   -8.9443

Example 3: Solve a Linear System

What it demonstrates: Linear system solving, formatted output

Prerequisites: None

% Solve Ax = b where A is 3x3 and b is [1; 2; 3]
A = [3 1 -1; 2 -2 1; 1 1 2];
b = [1; 2; 3];
x = A \ b;

fprintf('Solution vector x:\n');
disp(x);

% Verify: compute residual
residual = norm(A*x - b);
fprintf('Residual (should be ~0): %.2e\n', residual);

Agent prompt: "Solve the linear system Ax=b where A is 3 1 -1]; [2 -2 1]; [1 1 2 and b is [1; 2; 3]"

Expected output:

Solution vector x:
    0.6667
    0.3333
    0.6667

Residual (should be ~0): 1.11e-16

Example 4: String & Cell Operations

What it demonstrates: Text manipulation, cell arrays

Prerequisites: None

% Work with strings and cells
names = {'Alice', 'Bob', 'Charlie'};
scores = [95, 87, 92];

for i = 1:length(names)
    fprintf('%s scored %d%%\n', names{i}, scores(i));
end

% String operations
msg = "MATLAB is powerful";
fprintf('Uppercase: %s\n', upper(msg));
fprintf('Reversed: %s\n', msg(end:-1:1));

Agent prompt: "Show me how to work with names and scores in MATLAB"

Expected output:

Alice scored 95%
Bob scored 87%
Charlie scored 92%
Uppercase: MATLAB IS POWERFUL
Reversed: lufecop si LBATAM

Example 5: Table Operations

What it demonstrates: Tabular data, table arrays

Prerequisites: None

% Create and manipulate a table
T = table({'Alice'; 'Bob'; 'Charlie'}, [25; 30; 28], ...
    'VariableNames', {'Name', 'Age'});

disp(T);
fprintf('\nAverage age: %.1f\n', mean(T.Age));
fprintf('Oldest person: %s (age %d)\n', ...
    T.Name{T.Age == max(T.Age)}, max(T.Age));

Agent prompt: "Create a table with names and ages, then find the oldest person"

Expected output:

    Name      Age
    ____      ___

    Alice     25 
    Bob       30 
    Charlie   28 

Average age: 27.7
Oldest person: Bob (age 30)

Plotting Examples

Example 6: Line Plot with Styling

What it demonstrates: 2D line plots, axis labels, legend, interactive Plotly conversion

Prerequisites: None

% Plot three sine waves with different frequencies
x = linspace(0, 2*pi, 300);
f1 = sin(x);
f2 = sin(2*x);
f3 = sin(0.5*x);

plot(x, f1, 'b-', 'LineWidth', 2, 'DisplayName', 'sin(x)');
hold on;
plot(x, f2, 'r--', 'LineWidth', 2, 'DisplayName', 'sin(2x)');
plot(x, f3, 'g:', 'LineWidth', 2.5, 'DisplayName', 'sin(0.5x)');
hold off;

xlabel('Angle (radians)');
ylabel('Amplitude');
title('Sine Waves at Different Frequencies');
legend('Location', 'northeast');
grid on;

Agent prompt: "Plot three sine waves: sin(x), sin(2x), and sin(0.5x) from 0 to 2π with different colors and line styles"

Expected output: Interactive Plotly chart with:

  • Three colored lines (blue solid, red dashed, green dotted)
  • Interactive legend (click to toggle traces)
  • Hover tooltips showing (x, y) coordinates
  • Zoom and pan controls

Example 7: 3D Surface Plot

What it demonstrates: 3D visualization, color mapping, Plotly conversion

Prerequisites: None

% Create and plot a 3D surface
[X, Y] = meshgrid(-5:0.2:5, -5:0.2:5);
Z = sin(sqrt(X.^2 + Y.^2)) ./ (sqrt(X.^2 + Y.^2) + 0.1);

surf(X, Y, Z, 'EdgeColor', 'none');
colormap('cool');
colorbar;
xlabel('X');
ylabel('Y');
zlabel('Z');
title('3D Surface: sin(r)/r');

Agent prompt: "Create a 3D surface plot of sin(r)/r where r = sqrt(x² + y²)"

Expected output: Interactive 3D Plotly surface with:

  • Color-mapped surface (cool colormap)
  • Colorbar legend
  • Rotatable 3D view
  • Hover values at each point

Example 8: Subplots (2×2 Grid)

What it demonstrates: Multiple subplot layout, different plot types

Prerequisites: None

x = linspace(0, 10, 100);

% Subplot 1: Line plot
subplot(2, 2, 1);
plot(x, sin(x), 'b-', 'LineWidth', 2);
title('Sine Wave');
xlabel('x'); ylabel('sin(x)');
grid on;

% Subplot 2: Bar chart
subplot(2, 2, 2);
categories = {'A', 'B', 'C', 'D'};
values = [10, 24, 36, 18];
bar(categories, values, 'FaceColor', [0.2 0.6 0.8]);
title('Bar Chart');
ylabel('Values');

% Subplot 3: Scatter plot
subplot(2, 2, 3);
scatter(randn(100,1), randn(100,1), 50, 'r', 'filled');
title('Scatter Plot');
xlabel('X'); ylabel('Y');
grid on;

% Subplot 4: Histogram
subplot(2, 2, 4);
data = randn(1000, 1);
histogram(data, 30, 'EdgeColor', 'k');
title('Histogram');
xlabel('Value'); ylabel('Frequency');

Agent prompt: "Create a 2×2 subplot grid with a line plot, bar chart, scatter plot, and histogram"

Expected output: Four-panel Plotly figure with proper axis domains and shared container


Example 9: Histogram with Statistics

What it demonstrates: Statistical visualization, customized appearance

Prerequisites: Statistics & Machine Learning Toolbox (optional for mean/std)

% Generate random data and plot histogram
rng(42);  % Reproducible results
data = normrnd(100, 15, 1000, 1);

histogram(data, 50, 'Normalization', 'pdf', ...
    'EdgeColor', 'black', 'FaceColor', [0.7 0.7 0.9]);
hold on;

% Overlay normal distribution
mu = mean(data);
sigma = std(data);
x = linspace(mu - 4*sigma, mu + 4*sigma, 200);
y = normpdf(x, mu, sigma);
plot(x, y, 'r-', 'LineWidth', 2.5);
hold off;

xlabel('Value');
ylabel('Probability Density');
title(sprintf('Histogram with Normal Distribution (μ=%.1f, σ=%.1f)', mu, sigma));
legend('Data', 'Normal Fit');
grid on;

Agent prompt: "Create a histogram of 1000 random values from a normal distribution with mean 100 and std 15, overlaid with the theoretical normal curve"

Expected output: Histogram with overlaid normal distribution curve, statistics in title


Signal Processing Examples

Example 10: FFT Frequency Analysis

What it demonstrates: Signal processing, FFT, frequency domain, multi-panel plotting

Prerequisites: Signal Processing Toolbox (optional; core functions work without it)

% Generate a signal with multiple frequency components
fs = 1000;  % Sampling rate (Hz)
t = 0:1/fs:1;  % 1 second of data
f1 = 50;    % 50 Hz component
f2 = 120;   % 120 Hz component
signal = sin(2*pi*f1*t) + 0.5*sin(2*pi*f2*t) + 0.1*randn(size(t));

% Compute FFT
N = length(signal);
Y = fft(signal);
freqs = (0:N-1) * fs / N;

% Plot time and frequency domains
subplot(2, 1, 1);
plot(t, signal, 'b-', 'LineWidth', 1);
xlabel('Time (s)');
ylabel('Amplitude');
title('Time Domain Signal');
grid on;

subplot(2, 1, 2);
plot(freqs(1:N/2), abs(Y(1:N/2))/N, 'r-', 'LineWidth', 2);
xlabel('Frequency (Hz)');
ylabel('Magnitude');
title('Frequency Domain (FFT)');
xlim([0 300]);
grid on;

Agent prompt: "Create a signal with 50 Hz and 120 Hz components, then show both time and frequency domain plots"

Expected output: Two-panel figure showing:

  • Time domain: oscillating waveform
  • Frequency domain: two peaks at 50 Hz and 120 Hz

Example 11: Low-Pass Filtering

What it demonstrates: Signal filtering, frequency response, before/after comparison

Prerequisites: Signal Processing Toolbox

% Design a low-pass Butterworth filter
fs = 1000;
cutoff_freq = 100;  % Hz
[b, a] = butter(4, cutoff_freq/(fs/2));

% Generate noisy signal
t = 0:1/fs:2;
clean_signal = sin(2*pi*50*t);
noise = 0.5*randn(size(t));
noisy_signal = clean_signal + noise;

% Apply filter
filtered_signal = filtfilt(b, a, noisy_signal);

% Plot results
subplot(3, 1, 1);
plot(t(1:500), noisy_signal(1:500), 'b-', 'LineWidth', 1);
title('Noisy Signal (50 Hz + noise)');
ylabel('Amplitude');
grid on;

subplot(3, 1, 2);
plot(t(1:500), filtered_signal(1:500), 'g-', 'LineWidth', 2);
title('Filtered Signal (Low-Pass, 100 Hz cutoff)');
ylabel('Amplitude');
grid on;

subplot(3, 1, 3);
plot(t(1:500), clean_signal(1:500), 'r--', 'LineWidth', 2);
title('Original Clean Signal (reference)');
ylabel('Amplitude');
xlabel('Time (s)');
grid on;

Agent prompt: "Design a 4th-order Butterworth low-pass filter with 100 Hz cutoff, apply it to a noisy 50 Hz signal, and show before/after/original"

Expected output: Three-panel figure with noisy, filtered, and clean signals


Advanced Examples: Async Jobs

Jobs exceeding sync_timeout (default 30s) are automatically promoted to async execution. Report progress with the built-in mcp_progress() helper.

Example 12: Monte Carlo π Estimation (with Progress)

What it demonstrates: Long-running async job, progress reporting, random sampling

Prerequisites: None

% Estimate π using Monte Carlo method
% This will auto-promote to async if it takes > 30 seconds

rng(42);  % Reproducible results
n_trials = 5e6;  % 5 million trials
inside_circle = 0;

% Update progress every 500k trials
for i = 1:n_trials
    x = rand();
    y = rand();
    if x^2 + y^2 <= 1
        inside_circle = inside_circle + 1;
    end
    
    % Report progress every 500k iterations
    if mod(i, 5e5) == 0
        progress_pct = (i / n_trials) * 100;
        msg = sprintf('Trial %d / %d (%.1f%%)', i, n_trials, progress_pct);
        mcp_progress(__mcp_job_id__, progress_pct, msg);
    end
end

% Final result
pi_estimate = 4 * inside_circle / n_trials;
fprintf('Estimated π: %.6f (actual: %.6f, error: %.6f%%)\n', ...
    pi_estimate, pi, abs(pi_estimate - pi)/pi * 100);

Agent prompt: "Estimate π using a Monte Carlo simulation with 5 million random points and report progress every 500,000 trials"

Agent experience:

  1. Agent calls execute_code with the above code
  2. Server returns immediately with job_id: "job-xyz123" and status running
  3. Agent polls get_job_status("job-xyz123") periodically:
    • After 10s: progress: 10%, message: "Trial 500000/5000000"
    • After 20s: progress: 20%, message: "Trial 1000000/5000000"
    • ...
  4. After ~60s: status: "completed", full result returned

Expected final output:

Estimated π: 3.141627 (actual: 3.141593, error: 0.001%)

Example 13: Large Matrix SVD Computation

What it demonstrates: Heavy computation, timeout handling, decomposition

Prerequisites: None

% Compute SVD of a large random matrix
fprintf('Creating 5000x5000 random matrix...\n');
A = randn(5000, 5000);

fprintf('Computing SVD (this may take 30+ seconds)...\n');
[U, S, V] = svd(A);

% Get singular values
singular_vals = diag(S);

fprintf('SVD complete.\n');
fprintf('Matrix size: %d x %d\n', size(A, 1), size(A, 2));
fprintf('Rank estimate (singular values > 1e-10): %d\n', sum(singular_vals > 1e-10));
fprintf('Condition number: %.2e\n', singular_vals(1) / singular_vals(end));

% Plot singular value decay
loglog(1:min(100, length(singular_vals)), singular_vals(1:min(100, length(singular_vals))), 'b.-');
xlabel('Index');
ylabel('Singular Value (log scale)');
title('Singular Value Spectrum (first 100)');
grid on;

Agent prompt: "Compute the SVD of a 5000×5000 random matrix and show the singular value spectrum"

Expected behavior:

  • Code takes ~40 seconds
  • Auto-promotes to async after ~30 seconds
  • Returns job ID immediately
  • Agent polls for completion
  • Final result includes SVD computation stats and plot

Example 14: Iterative Solver with Progress

What it demonstrates: Iterative algorithm, intermediate progress updates

Prerequisites: None

% Solve Ax = b using conjugate gradient-like iteration (simplified)
n = 1000;
A = diag(100:100+n-1);  % Diagonal matrix with eigenvalues 100..1099
b = randn(n, 1);

x = zeros(n, 1);  % Initial guess
r = b - A*x;
p = r;
rsold = r' * r;

max_iter = 500;
tolerance = 1e-6;

for i = 1:max_iter
    Ap = A * p;
    alpha = rsold / (p' * Ap);
    x = x + alpha * p;
    r = r - alpha * Ap;
    rsnew = r' * r;
    
    % Report progress
    if mod(i, 50) == 0 || i == 1
        residual = sqrt(rsnew);
        progress_pct = (i / max_iter) * 100;
        msg = sprintf('Iteration %d: residual = %.2e', i, residual);
        mcp_progress(__mcp_job_id__, progress_pct, msg);
    end
    
    if rsnew < tolerance^2
        fprintf('Converged in %d iterations\n', i);
        break;
    end
    
    beta = rsnew / rsold;
    p = r + beta * p;
    rsold = rsnew;
end

fprintf('Final residual: %.2e\n', sqrt(rsnew));
fprintf('Solution norm: %.2f\n', norm(x));

Agent prompt: "Solve a large system of linear equations using an iterative solver, reporting progress every 50 iterations"

Expected output:

Iteration 50: residual = 1.23e-04
Iteration 100: residual = 2.45e-06
...
Converged in 147 iterations
Final residual: 9.87e-07
Solution norm: 3.45

File Management Examples

Example 15: Upload and Process Data

What it demonstrates: File upload, data reading, processing

Prerequisites: None

% Assume agent uploaded a CSV file named 'data.csv'
% Read it back from the session temp directory

data_file = fullfile(__mcp_temp_dir__, 'data.csv');
fprintf('Reading data from: %s\n', data_file);

% Read CSV (simple tab/comma-separated)
data = readmatrix(data_file);
fprintf('Data shape: %d rows × %d columns\n', size(data, 1), size(data, 2));

% Compute statistics
fprintf('Column means: %s\n', mat2str(mean(data), 3));
fprintf('Column stds:  %s\n', mat2str(std(data), 3));
fprintf('Min values:   %s\n', mat2str(min(data), 3));
fprintf('Max values:   %s\n', mat2str(max(data), 3));

% Generate a plot
boxplot(data);
xlabel('Column');
ylabel('Value');
title('Data Distribution by Column');

Agent workflow:

  1. Upload CSV file: upload_data("data.csv", base64_content)
  2. Execute the MATLAB code above
  3. Read output stats and generated plot
  4. Agent can retrieve saved images with read_image()

Example 16: Save and Retrieve Results

What it demonstrates: Writing files, saving figures, data retrieval

Prerequisites: None

% Generate results and save to session temp directory
results_dir = __mcp_temp_dir__;

% Create synthetic data
x = linspace(0, 10, 100);
y = sin(x) + 0.1*randn(size(x));

% Save as CSV
csv_file = fullfile(results_dir, 'analysis_results.csv');
writematrix([x' y'], csv_file);
fprintf('Saved CSV: %s\n', csv_file);

% Save as MAT file
mat_file = fullfile(results_dir, 'analysis_results.mat');
save(mat_file, 'x', 'y');
fprintf('Saved MAT: %s\n', mat_file);

% Create and save a figure
plot(x, y, 'b.', 'MarkerSize', 8);
hold on;
plot(x, sin(x), 'r-', 'LineWidth', 2);
hold off;
xlabel('x'); ylabel('y');
title('Data vs Fit');
legend('Noisy Data', 'True Curve');
grid on;

fig_file = fullfile(results_dir, 'analysis_plot.png');
saveas(gcf, fig_file);
fprintf('Saved figure: %s\n', fig_file);

% List all files in temp directory
files = dir(results_dir);
fprintf('\nFiles in temp directory:\n');
for i = 1:length(files)
    if ~files(i).isdir
        fprintf('  - %s (%d bytes)\n', files(i).name, files(i).bytes);
    end
end

Agent workflow:

  1. Execute code above
  2. Server returns list of files created
  3. Agent retrieves CSV: read_data("analysis_results.csv")
  4. Agent retrieves plot: read_image("analysis_plot.png")
  5. Agent retrieves MAT summary: read_data("analysis_results.mat", format="summary")

Example 17: Generate MATLAB Script Dynamically

What it demonstrates: Script generation, syntax, readability

Prerequisites: None

% Generate a reusable MATLAB script dynamically

script_dir = __mcp_temp_dir__;
script_file = fullfile(script_dir, 'auto_generated_analysis.m');

% Build script content
script_content = [
    "% Auto-generated analysis script\n"
    "% Generated by MATLAB MCP Server\n\n"
    "function results = auto_generated_analysis(input_data)\n"
    "    %% Compute statistics\n"
    "    results.mean = mean(input_data);\n"
    "    results.std = std(input_data);\n"
    "    results.median = median(input_data);\n"
    "    results.iqr = iqr(input_data);\n"
    "    \n"
    "    %% Create visualization\n"
    "    figure('Visible', 'off');\n"
    "    histogram(input_data, 30);\n"
    "    title('Distribution of Input Data');\n"
    "    xlabel('Value');\n"
    "    ylabel('Frequency');\n"
    "    \n"
    "    print(gcf, '-dpng', 'distribution.png');\n"
    "    close(gcf);\n"
    "end\n"
];

% Write script
fid = fopen(script_file, 'w');
fprintf(fid, script_content);
fclose(fid);

fprintf('Generated script: %s\n', script_file);
fprintf('Script size: %d bytes\n', numel(script_content));

Agent can then retrieve: read_script("auto_generated_analysis.m")


Configuration Examples

Minimal Configuration (Single User, Local)

File: config.yaml

server:
  transport: "stdio"
  log_level: "info"

pool:
  min_engines: 1
  max_engines: 2

execution:
  sync_timeout: 30
  max_execution_time: 600

sessions:
  max_sessions: 1

security:
  blocked_functions_enabled: true

Use case: Local agent testing, single MATLAB license


Multi-User Server Configuration

File: config.yaml

server:
  transport: "sse"
  host: "127.0.0.1"  # Bind to localhost only; expose via reverse proxy
  port: 8765
  log_level: "info"

pool:
  min_engines: 4
  max_engines: 16
  scale_down_idle_timeout: 600
  health_check_interval: 30

execution:
  sync_timeout: 30
  max_execution_time: 3600
  temp_dir: "/var/matlab-mcp/temp"

sessions:
  max_sessions: 50
  session_timeout: 3600
  job_retention_seconds: 86400

security:
  blocked_functions_enabled: true
  require_proxy_auth: true
  max_upload_size_mb: 500

output:
  plotly_conversion: true
  thumbnail_enabled: true

monitoring:
  enabled: true
  sample_interval: 10
  dashboard_enabled: true
  http_port: 8766

Use case: Shared server serving multiple concurrent AI agents behind reverse proxy (nginx)


High-Performance Configuration

File: config.yaml

pool:
  min_engines: 8
  max_engines: 32
  scale_down_idle_timeout: 300
  health_check_interval: 15
  proactive_warmup_threshold: 0.7
  queue_max_size: 200

execution:
  sync_timeout: 60
  max_execution_time: 7200
  workspace_isolation: true
  engine_affinity: true

sessions:
  max_sessions: 200

monitoring:
  enabled: true
  sample_interval: 5
  retention_days: 14

Use case: High-throughput production workloads


Data Flow Diagram

sequenceDiagram
    participant A as AI Agent
    participant S as Server
    participant J as Job Executor
    participant P as Engine Pool
    participant E as MATLAB Engine
    
    A->>S: execute_code(code)
    activate S
    S->>S: Validate security
    S->>J: Create job
    J->>P: Acquire engine
    activate P
    P->>E: (get or start)
    activate E
    P-->>J: Engine handle
    deactivate P
    J->>E: eval(code, stdout)
    E-->>J: Results
    deactivate E
    J->>J: Format output
    deactivate S
    J-->>S: Result dict
    S-->>A: MCP response
    
    P->>E: Release engine
    Note over P: Engine returns to IDLE pool
Loading

Quick Reference: Tool Signatures

Tool Input Output Notes
execute_code(code) MATLAB string Job ID or inline result Auto-async if > 30s
check_code(code) MATLAB string Linting issues Static analysis
get_workspace() Variable list Current session only
get_job_status(job_id) Job ID Status, progress Poll for updates
upload_data(filename, content_base64) File + data Path, size Max 100 MB default
read_data(filename, format) Filename Content or summary .mat, .csv, .xlsx
read_image(filename) Image file Image content .png, .jpg, .gif
list_files() Directory contents Session temp dir
get_pool_status() Pool metrics Engines, utilization
get_server_health() Health status Healthy / degraded / unhealthy

Troubleshooting Examples

Example: Code Blocked by Security Policy

% This will be REJECTED with BlockedFunctionError:
system('ls -la')

Agent message: "Blocked function: system() is not allowed. Use MATLAB alternatives like dir() for directory listing."

Workaround:

% Use MATLAB functions instead
files = dir('.');
disp({files.name}')

Example: Job Timeout

% This will auto-promote to async (takes ~45 seconds):
tic
pause(45)
toc

Agent experience:

  1. Receives job_id: "job-abc" with status running
  2. Polls get_job_status("job-abc") until completion
  3. Once done, calls get_job_result("job-abc") for full output

Example: Large Result Truncation

% Generate very large output (won't all be returned inline)
big_data = randn(10000, 10000);
disp(big_data);  % Would be huge!

Server behavior:

  1. Captures output
  2. Detects truncation needed (> 50KB)
  3. Saves to file in temp directory
  4. Returns output_saved: "output_large.txt" in response
  5. Agent can retrieve with read_data("output_large.txt")

This Examples page provides progressions from basic to advanced scenarios. For more help, refer to the Configuration Guide, Async Jobs, and Custom Tools sections.

Clone this wiki locally