API Server
Xberg runs as an HTTP REST API server (xberg serve) or as an MCP server (xberg mcp) for AI agent integration.
HTTP REST API
Section titled “HTTP REST API”# Default: http://127.0.0.1:8000xberg serve
# Custom host and portxberg serve -H 0.0.0.0 -p 3000
# With configuration filexberg serve --config xberg.toml# Run server on port 8000docker run -d \n -p 8000:8000 \n ghcr.io/xberg-io/xberg:latest \n serve -H 0.0.0.0 -p 8000
# With environment variablesdocker run -d \n -e XBERG_CORS_ORIGINS="https://myapp.com" \n -e XBERG_MAX_MULTIPART_FIELD_BYTES=209715200 \n -p 8000:8000 \n ghcr.io/xberg-io/xberg:latest \n serve -H 0.0.0.0 -p 8000# Start serverimport subprocesssubprocess.Popen(["python", "-m", "xberg", "serve", "-H", "0.0.0.0", "-p", "8000"])use xberg::{ExtractionConfig, api::serve_with_config};
#[tokio::main]async fn main() -> xberg::Result<()> { let config = ExtractionConfig::discover()?.unwrap_or_default(); serve_with_config("0.0.0.0", 8000, config).await?; Ok(())}package main
import ( "log" "os/exec")
func main() { cmd := exec.Command("xberg", "serve", "-H", "0.0.0.0", "-p", "8000") cmd.Stdout = log.Writer() cmd.Stderr = log.Writer() if err := cmd.Run(); err != nil { log.Fatalf("failed to start server: %v", err) }}import java.io.IOException;
public class ApiServer { public static void main(String[] args) { try { ProcessBuilder pb = new ProcessBuilder( "xberg", "serve", "-H", "0.0.0.0", "-p", "8000" ); pb.inheritIO(); Process process = pb.start(); process.waitFor(); } catch (IOException | InterruptedException e) { System.err.println("Failed to start server: " + e.getMessage()); } }}using System;using System.Diagnostics;
class ApiServer{ static void Main() { var processInfo = new ProcessStartInfo { FileName = "xberg", Arguments = "serve -H 0.0.0.0 -p 8000", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true };
using (var process = Process.Start(processInfo)) { process?.WaitForExit(); } }}Endpoints
Section titled “Endpoints”POST /extract
Section titled “POST /extract”Extract text from uploaded files via multipart form data.
| Field | Required | Description |
|---|---|---|
files |
Yes (repeatable) | Files to extract |
config |
No | JSON config overrides |
output_format |
No | plain (default), markdown, djot, or html |
# Single filecurl -F "files=@document.pdf" http://localhost:8000/extract
# Multiple filescurl -F "files=@doc1.pdf" -F "files=@doc2.docx" http://localhost:8000/extract
# With config overridescurl -F "files=@scanned.pdf" \ -F 'config={"ocr":{"language":"eng"},"force_ocr":true}' \ http://localhost:8000/extract{ "results": [ { "content": "Extracted text...", "mime_type": "application/pdf", "metadata": { "page_count": 10, "author": "John Doe" }, "tables": [], "detected_languages": ["eng"], "chunks": null, "images": null } ], "errors": [], "summary": { "inputs": 1, "results": 1, "errors": 0 }}POST /extract-async
Section titled “POST /extract-async”Queue an extraction job and return immediately. Accepts the same multipart form
data or JSON body as /extract. Returns 202 Accepted with a job identifier.
Returns 429 Too Many Requests when the concurrent job limit is reached.
curl -F "files=@document.pdf" http://localhost:8000/extract-async{ "job_id": "550e8400-e29b-41d4-a716-446655440000" }GET /jobs/{job_id}
Section titled “GET /jobs/{job_id}”Poll the status of an async job. state is one of pending, running,
completed, or failed. The result field is present only when
state == completed; the error field only when state == failed. Jobs
expire after 5 minutes and return 404 once evicted.
curl http://localhost:8000/jobs/550e8400-e29b-41d4-a716-446655440000{ "job_id": "550e8400-e29b-41d4-a716-446655440000", "state": "completed", "created_at": "2026-07-07T12:00:00Z", "updated_at": "2026-07-07T12:00:03Z", "result": { "results": [], "errors": [], "summary": {} }}Other Endpoints
Section titled “Other Endpoints”| Endpoint | Method | Description |
|---|---|---|
/health |
GET | {"status":"healthy","version":"1.0.0-rc.14"} |
/version |
GET | {"version":"1.0.0-rc.14"} |
/detect |
POST | MIME type detection (multipart) |
/formats |
GET | List supported formats |
/cache/stats |
GET | Cache statistics |
/cache/warm |
POST | Pre-download models |
/cache/manifest |
GET | Model manifest with checksums |
/cache/clear |
DELETE | Clear all cached files |
/info |
GET | {"version":"...","rust_backend":true} |
/openapi.json |
GET | OpenAPI 3.1 schema |
Client Examples
Section titled “Client Examples”import asyncioimport json
import httpx
async def main() -> None: async with httpx.AsyncClient() as client, open("document.pdf", "rb") as f: response = await client.post( "http://localhost:8000/extract", files={"files": f}, ) data = response.json() print(json.dumps(data, indent=2))
asyncio.run(main())// Using fetch APIconst formData = new FormData();formData.append("files", fileInput.files[0]);
const response = await fetch("http://localhost:8000/extract", { method: "POST", body: formData,});
const results = await response.json();console.log(results[0].content);use std::path::Path;
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = reqwest::Client::new(); let bytes = tokio::fs::read("document.pdf").await?; let file_name = Path::new("document.pdf") .file_name() .and_then(|n| n.to_str()) .unwrap_or("document.pdf");
let part = reqwest::multipart::Part::bytes(bytes) .file_name(file_name.to_string()) .mime_str("application/pdf")?; let form = reqwest::multipart::Form::new().part("file", part);
let response = client .post("http://localhost:8000/extract") .multipart(form) .send() .await?;
let result: serde_json::Value = response.error_for_status()?.json().await?; println!("{}", result["content"].as_str().unwrap_or("")); Ok(())}package main
import ( "bytes" "io" "log" "mime/multipart" "net/http" "os")
func main() { file, err := os.Open("document.pdf") if err != nil { log.Fatalf("failed to open file: %v", err) } defer file.Close()
body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("files", "document.pdf") io.Copy(part, file) writer.Close()
resp, err := http.Post("http://localhost:8000/extract", writer.FormDataContentType(), body) if err != nil { log.Fatalf("request failed: %v", err) } defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)}import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.URI;import java.nio.file.Files;import java.nio.file.Paths;
HttpClient client = HttpClient.newHttpClient();
try (var fileStream = Files.newInputStream(Paths.get("document.pdf"))) { byte[] content = fileStream.readAllBytes(); var request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8000/extract")) .header("Content-Type", "application/octet-stream") .POST(HttpRequest.BodyPublishers.ofByteArray(content)) .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());}using System;using System.IO;using System.Net.Http;
var client = new HttpClient();
using (var fileStream = File.OpenRead("document.pdf")){ using (var content = new MultipartFormDataContent()) { content.Add(new StreamContent(fileStream), "files", "document.pdf");
var response = await client.PostAsync("http://localhost:8000/extract", content); var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json); }}require 'net/http'require 'json'
uri = URI('http://localhost:8000/extract')http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri)
File.open('document.pdf', 'rb') do |file| body = file.read request['Content-Type'] = 'application/octet-stream' request.body = body
response = http.request(request)
if response.is_a?(Net::HTTPSuccess) data = JSON.parse(response.body) puts JSON.pretty_generate(data) else puts "Error: #{response.code} #{response.message}" endendError Handling
Section titled “Error Handling”{ "error_type": "ValidationError", "message": "Invalid file format", "status_code": 400}| Status | Error type | Meaning |
|---|---|---|
| 400 | ValidationError |
Invalid input |
| 422 | ParsingError, OcrError |
Processing failed |
| 500 | Internal errors | Server errors |
import httpx
try: with httpx.Client() as client: with open("document.pdf", "rb") as f: files: dict = {"files": f} response: httpx.Response = client.post( "http://localhost:8000/extract", files=files ) response.raise_for_status() results: list = response.json() print(f"Extracted {len(results)} documents")except httpx.HTTPStatusError as e: error: dict = e.response.json() error_type: str = error.get("error_type", "Unknown") message: str = error.get("message", "No message") print(f"Error: {error_type}: {message}")import { readFileSync } from "node:fs";
async function extractDocument(): Promise<void> { const formData = new FormData(); const fileData = readFileSync("document.pdf"); formData.append("files", new Blob([fileData]), "document.pdf");
try { const response = await fetch("http://localhost:8000/extract", { method: "POST", body: formData, });
if (!response.ok) { const error = await response.json(); console.error(`Error: ${error.error_type}: ${error.message}`); return; }
const results = await response.json(); console.log(`Extracted ${results.length} documents`); } catch (error: unknown) { if (error instanceof Error) { console.error(`Request failed: ${error.message}`); } }}
extractDocument();use xberg::{extract, ExtractInput, ExtractionConfig, XbergError, Result};
async fn extract_text(bytes: &[u8], mime_type: &str) -> Result<String> { let config = ExtractionConfig::default(); let output = extract( ExtractInput::from_bytes(bytes.to_vec(), mime_type, Some("document.pdf".to_string())), &config, ) .await?;
Ok(output .results .first() .map(|document| document.content.clone()) .unwrap_or_default())}
#[tokio::main]async fn main() { let bytes = std::fs::read("document.pdf").unwrap_or_default(); match extract_text(&bytes, "application/pdf").await { Ok(text) => println!("Extracted {} chars", text.len()), Err(XbergError::UnsupportedFormat(mime)) => { eprintln!("Format not supported: {mime}"); } Err(XbergError::Ocr { message, .. }) => { eprintln!("OCR failed: {message}"); } Err(e) => eprintln!("Error: {e}"), }}package main
import ( "bytes" "encoding/json" "io" "log" "mime/multipart" "net/http" "os")
func main() { file, err := os.Open("document.pdf") if err != nil { log.Fatalf("failed to open file: %v", err) } defer file.Close()
body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("files", "document.pdf") io.Copy(part, file) writer.Close()
resp, err := http.Post("http://localhost:8000/extract", writer.FormDataContentType(), body) if err != nil { log.Fatalf("request failed: %v", err) } defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { var errResp map[string]string json.NewDecoder(resp.Body).Decode(&errResp) log.Fatalf("error: %s: %s", errResp["error_type"], errResp["message"]) }
var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) println("Success:", result["content"].(string))}import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.URI;import java.nio.file.Files;import java.nio.file.Paths;import com.fasterxml.jackson.databind.ObjectMapper;
HttpClient client = HttpClient.newHttpClient();byte[] fileBytes = Files.readAllBytes(Paths.get("document.pdf"));
var request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8000/extract")) .header("Content-Type", "application/octet-stream") .POST(HttpRequest.BodyPublishers.ofByteArray(fileBytes)) .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) { ObjectMapper mapper = new ObjectMapper(); var error = mapper.readTree(response.body()); System.err.println("Error: " + error.get("error_type").asText() + " - " + error.get("message").asText());} else { System.out.println("Success: " + response.body());}using System;using System.IO;using System.Net.Http;using System.Text.Json;
var client = new HttpClient();
try{ using (var fileStream = File.OpenRead("document.pdf")) { using (var content = new MultipartFormDataContent()) { content.Add(new StreamContent(fileStream), "files", "document.pdf");
var response = await client.PostAsync("http://localhost:8000/extract", content);
if (!response.IsSuccessStatusCode) { var errorJson = await response.Content.ReadAsStringAsync(); var errorDoc = JsonDocument.Parse(errorJson); var errorType = errorDoc.RootElement.GetProperty("error_type").GetString(); var message = errorDoc.RootElement.GetProperty("message").GetString();
Console.WriteLine($"Error: {errorType}: {message}"); return; }
var json = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Success: {json}"); } }}catch (HttpRequestException e){ Console.WriteLine($"Request failed: {e.Message}");}require 'xberg'
begin pdf_bytes = File.read('document.pdf') config = Xberg::ExtractionConfig.new
input = Xberg::ExtractInput.from_bytes(pdf_bytes, 'application/pdf') output = Xberg.extract(input, config) result = output.results.first puts "Extracted #{result.content.length} characters"rescue RuntimeError => e # All extraction errors are raised as RuntimeError # Check error message for details case e.message when /parse|parsing/i puts "Failed to parse document: #{e.message}" when /ocr/i puts "OCR processing failed: #{e.message}" when /validation|invalid/i puts "Invalid configuration: #{e.message}" else puts "Extraction error: #{e.message}" endendConfiguration
Section titled “Configuration”The server discovers xberg.toml in the current and parent directories. Pass --config path/to/file to use a different file.
| Variable | Default | Description |
|---|---|---|
XBERG_MAX_REQUEST_BODY_BYTES |
104857600 |
Max request body size in bytes |
XBERG_MAX_MULTIPART_FIELD_BYTES |
104857600 |
Max multipart field size in bytes |
XBERG_CORS_ORIGINS |
* |
Comma-separated allowed origins |
See Configuration Guide for all options.
MCP Server
Section titled “MCP Server”xberg mcpxberg mcp --config xberg.tomlimport subprocessimport timefrom typing import Optional
mcp_process: subprocess.Popen = subprocess.Popen( ["python", "-m", "xberg", "mcp"], stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
pid: Optional[int] = mcp_process.pidprint(f"MCP server started with PID: {pid}")
time.sleep(1)print("Server is running, listening for connections")import { spawn } from "child_process";
const mcpProcess = spawn("xberg", ["mcp"]);
mcpProcess.stdout.on("data", (data) => { console.log(`MCP Server: ${data}`);});
mcpProcess.stderr.on("data", (data) => { console.error(`MCP Error: ${data}`);});
mcpProcess.on("error", (err) => { console.error(`Failed to start MCP server: ${err.message}`);});use xberg::{ExtractionConfig, mcp::start_mcp_server_with_config};
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let config = ExtractionConfig::discover()?; start_mcp_server_with_config(config).await?; Ok(())}package main
import ( "fmt" "os" "os/exec")
func main() { cmd := exec.Command("xberg", "mcp") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "Failed to start MCP server: %v\n", err) }}import java.io.IOException;
public class McpServer { public static void main(String[] args) { try { // Start MCP server using CLI ProcessBuilder pb = new ProcessBuilder("xberg", "mcp"); pb.inheritIO(); Process process = pb.start(); process.waitFor(); } catch (IOException | InterruptedException e) { System.err.println("Failed to start MCP server: " + e.getMessage()); } }}using System;using System.Diagnostics;using System.Threading.Tasks;
var processInfo = new ProcessStartInfo{ FileName = "xberg", Arguments = "mcp", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true};
var mcpProcess = Process.Start(processInfo);
Console.WriteLine($"MCP server started with PID: {mcpProcess?.Id}");await Task.Delay(1000);Console.WriteLine("Server is running, listening for connections");
mcpProcess?.WaitForExit();require 'open3'
begin Open3.popen3('xberg', 'mcp') do |stdin, stdout, stderr, wait_thr| puts stdout.read wait_thr.join endrescue => e puts "Failed to start MCP server: #{e.message}"endThe MCP server exposes extract, extract_batch, detect_mime_type, list_formats, get_version, and the cache_* tools. See the MCP Reference for the full tool list, parameters, and schemas.
All extraction tools accept an optional config object. URI and byte payload details live in ExtractInput as kind = "uri" or kind = "bytes".
Batch Extraction
Section titled “Batch Extraction”import asyncioimport jsonfrom mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_client
async def main() -> None: server_params: StdioServerParameters = StdioServerParameters( command="xberg", args=["mcp"] )
inputs: list[dict[str, str]] = [ {"kind": "uri", "uri": "file1.pdf"}, {"kind": "uri", "uri": "file2.docx"}, {"kind": "uri", "uri": "notes.md"}, ] config: dict[str, bool] = {"use_cache": True}
async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize()
result = await session.call_tool( "extract_batch", arguments={"inputs": inputs, "config": config}, )
payload_text: str = result.content[0].text batch: dict = json.loads(payload_text)
print(f"Extracted {batch['summary']['results']} files") for index, item in enumerate(batch["results"], start=1): mime_type: str | None = item.get("mime_type") preview: str = item["content"][:80].replace("\n", " ") print(f" [{index}] {mime_type or 'unknown'}: {preview}...")
asyncio.run(main())AI Agent Integration
Section titled “AI Agent Integration”Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{ "mcpServers": { "xberg": { "command": "xberg", "args": ["mcp"] } }}import asynciofrom mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_client
async def main() -> None: server_params: StdioServerParameters = StdioServerParameters( command="xberg", args=["mcp"] )
async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() tool_names: list[str] = [t.name for t in tools.tools] print(f"Available tools: {tool_names}") result = await session.call_tool( "extract", arguments={"path": "document.pdf", "async": True} ) print(result)
asyncio.run(main())import asyncio
import httpxfrom mcp import ClientSessionfrom mcp.client.streamable_http import streamable_http_client
MCP_URL = "http://127.0.0.1:8001/mcp"
async def main() -> None: # Requires MCP server running with HTTP transport: # xberg mcp --transport http --host 127.0.0.1 --port 8001
async with httpx.AsyncClient(follow_redirects=True) as http_client: async with streamable_http_client(MCP_URL, http_client=http_client) as ( read, write, ): async with ClientSession(read, write) as session: await session.initialize()
tools = await session.list_tools() tool_names: list[str] = [t.name for t in tools.tools] print(f"Available tools: {tool_names}")
result = await session.call_tool( "extract", arguments={"path": "document.pdf"}, ) print(result)
asyncio.run(main())from langchain.agents import initialize_agent, AgentTypefrom langchain.tools import Toolfrom langchain_openai import ChatOpenAIimport subprocessimport json
mcp_process = subprocess.Popen( ["xberg", "mcp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,)
def extract(path: str) -> str: request: dict = { "method": "tools/call", "params": { "name": "extract", "arguments": {"path": path, "async": True}, }, } mcp_process.stdin.write(json.dumps(request).encode() + b"\n") mcp_process.stdin.flush() response = mcp_process.stdout.readline() return json.loads(response)["result"]["content"]
tools: list[Tool] = [ Tool(name="extract_document", func=extract, description="Extract")]
llm = ChatOpenAI(temperature=0)agent = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)import { spawn } from "child_process";import * as readline from "readline";
const mcpProcess = spawn("xberg", ["mcp"]);
const rl = readline.createInterface({ input: mcpProcess.stdout, output: mcpProcess.stdin, terminal: false,});
const request = { method: "tools/call", params: { name: "extract", arguments: { path: "document.pdf", async: true, }, },};
mcpProcess.stdin.write(JSON.stringify(request) + "\n");
rl.on("line", (line) => { const response = JSON.parse(line); console.log(response); mcpProcess.kill();});
mcpProcess.on("error", (err) => { console.error("Failed to start MCP process:", err);});use serde_json::json;use std::io::{BufRead, BufReader, Write};use std::process::{Command, Stdio};
fn main() -> Result<(), Box<dyn std::error::Error>> { let mut child = Command::new("xberg") .arg("mcp") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?;
{ let stdin = child.stdin.as_mut().ok_or("Failed to open stdin")?; let request = json!({ "method": "tools/call", "params": { "name": "extract", "arguments": { "path": "document.pdf", "async": true } } }); stdin.write_all(request.to_string().as_bytes())?; stdin.write_all(b"\n")?; }
let stdout = child.stdout.take().ok_or("Failed to open stdout")?; let reader = BufReader::new(stdout); for line in reader.lines() { if let Ok(line) = line { println!("{}", line); break; } }
child.wait()?; Ok(())}package main
import ( "bufio" "encoding/json" "fmt" "log" "os/exec")
type MCPRequest struct { Method string `json:"method"` Params MCPParams `json:"params"`}
type MCPParams struct { Name string `json:"name"` Arguments map[string]interface{} `json:"arguments"`}
func main() { cmd := exec.Command("xberg", "mcp") stdin, err := cmd.StdinPipe() if err != nil { log.Fatalf("create stdin pipe: %v", err) } stdout, err := cmd.StdoutPipe() if err != nil { log.Fatalf("create stdout pipe: %v", err) }
if err := cmd.Start(); err != nil { log.Fatalf("start command: %v", err) }
request := MCPRequest{ Method: "tools/call", Params: MCPParams{ Name: "extract", Arguments: map[string]interface{}{ "path": "document.pdf", "async": true, }, }, }
data, err := json.Marshal(request) if err != nil { log.Fatalf("marshal request: %v", err) } fmt.Fprintf(stdin, "%s\n", string(data))
scanner := bufio.NewScanner(stdout) if scanner.Scan() { fmt.Println(scanner.Text()) }
if err := cmd.Wait(); err != nil { log.Fatalf("wait for command: %v", err) }}import com.fasterxml.jackson.databind.ObjectMapper;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.util.Map;
public class McpClient { private final Process mcpProcess; private final BufferedWriter stdin; private final BufferedReader stdout; private final ObjectMapper mapper = new ObjectMapper();
public McpClient() throws IOException { ProcessBuilder pb = new ProcessBuilder("xberg", "mcp"); mcpProcess = pb.start(); stdin = new BufferedWriter(new OutputStreamWriter(mcpProcess.getOutputStream())); stdout = new BufferedReader(new InputStreamReader(mcpProcess.getInputStream())); }
// Note: This is a custom RPC client method, not Xberg.extract() API public String extract(String path) throws IOException { Map<String, Object> request = Map.of( "method", "tools/call", "params", Map.of( "name", "extract", "arguments", Map.of("path", path, "async", true) ) );
stdin.write(mapper.writeValueAsString(request)); stdin.newLine(); stdin.flush();
String response = stdout.readLine(); @SuppressWarnings("unchecked") Map<String, Object> result = mapper.readValue(response, Map.class); @SuppressWarnings("unchecked") Map<String, Object> resultData = (Map<String, Object>) result.get("result"); return (String) resultData.get("content"); }
public void close() throws IOException { stdin.close(); stdout.close(); mcpProcess.destroy(); }
public static void main(String[] args) { try (McpClient client = new McpClient()) { String content = client.extract("contract.pdf"); System.out.println("Extracted content: " + content); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); } }}using System;using System.Diagnostics;using System.IO;using System.Threading.Tasks;
var processInfo = new ProcessStartInfo{ FileName = "xberg", Arguments = "mcp", UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true};
var process = Process.Start(processInfo);
var clientInput = process.StandardInput;var clientOutput = process.StandardOutput;
// Initialize session by sending initialize requestvar initRequest = new{ jsonrpc = "2.0", id = 1, method = "initialize", parameters = new { }};
await clientInput.WriteLineAsync(System.Text.Json.JsonSerializer.Serialize(initRequest));await clientInput.FlushAsync();
var initResponse = await clientOutput.ReadLineAsync();Console.WriteLine($"Init response: {initResponse}");
// List available toolsvar listRequest = new{ jsonrpc = "2.0", id = 2, method = "tools/list"};
await clientInput.WriteLineAsync(System.Text.Json.JsonSerializer.Serialize(listRequest));await clientInput.FlushAsync();
var listResponse = await clientOutput.ReadLineAsync();Console.WriteLine($"Available tools: {listResponse}");
process?.WaitForExit();require 'json'require 'open3'
Open3.popen3('xberg', 'mcp') do |stdin, stdout, stderr, wait_thr| request = { method: 'tools/call', params: { name: 'extract', arguments: { path: 'document.pdf', async: true } } }
stdin.puts JSON.generate(request) stdin.close_write
response = stdout.gets result = JSON.parse(response) puts JSON.pretty_generate(result)endFor container deployment, see the Docker Guide.