The integration of Large Language Models (LLMs) with external databases, services, and APIs represents the frontier of modern AI engineering. To facilitate this integration, the industry has rallied around protocols like the Model Context Protocol (MCP). While MCP provides a robust standard for connecting LLMs to external data sources, it introduces a non-trivial engineering tax. Building, deploying, and maintaining a dedicated MCP server requires infrastructure, boilerplate code, and continuous operational oversight.
For the vast majority of web integrations, this overhead is unnecessary. Most APIs do not require complex, custom-built runtimes; they simply require executing standard HTTP requests with specific parameters and headers.
Open Assistant introduces a declarative alternative to the traditional MCP server model. By shifting from imperative integration code to a structured JSON configuration, the system allows developers to transform any standard REST API into a fully functional tool suite for the AI assistant. No server code, no deployment pipelines, and no infrastructure management are required. The JSON configuration is the integration layer.
The Integration Bottleneck: The Weight of MCP Servers
The rise of agentic AI workflows has highlighted a fundamental limitation: LLMs are highly capable reasoners but remain isolated from real-time data and transactional systems unless explicitly bridged. The Model Context Protocol (MCP) successfully standardizes this bridge, establishing a reliable protocol for tool discovery, resource sharing, and prompt templates.
However, implementing an MCP server introduces several engineering challenges:
- The Infrastructure Tax: To connect an LLM to a simple service (such as a timesheet tracker or a system status page), developers must write a dedicated server application (typically in Node.js or Python), containerize it, deploy it to a cloud provider, and manage its scaling, security, and uptime.
- The "Wrapper of a Wrapper" Problem: Most external services already expose highly optimized, secure, and scalable REST APIs. Building an MCP server on top of these services creates an unnecessary proxy layer that merely translates JSON-RPC requests from the LLM orchestrator into HTTP requests to the target API, and back again.
- Maintenance and Security Debt: Every custom server represents a new attack surface and a long-term maintenance liability. Developers must manage dependencies, rotate credentials securely, handle network timeouts, and debug connection errors across multiple layers of the stack.
In practice, most integrations require a simple pattern: call a specific endpoint, pass defined parameters, and return the payload to the model. Recognizing this pattern allows for a more elegant architecture: one where the AI assistant's core engine natively understands how to communicate with standard APIs using a declarative schema.
The Open Assistant Paradigm: Declarative Integration
Open Assistant resolves this integration bottleneck by replacing imperative adapter code with a single, declarative JSON schema. Instead of writing a server to bridge the gap, developers write a static JSON file that describes the target API.
This approach shifts the responsibility of protocol translation, parameter mapping, and request execution entirely to the assistant's runtime engine.
The "Zero-Deployment" Advantage
When a plugin is loaded into Open Assistant, the engine dynamically parses the schema at runtime. It registers the defined endpoints as native tools, translates the JSON parameter definitions into the precise schemas expected by the LLM, and manages the network lifecycle of every request.
Because the integration is entirely declarative, there is no code to compile, no container to deploy, and no server to monitor. The integration layer scales automatically with the host assistant, inheriting its security posture, logging infrastructure, and error handling.
Anatomy of an Open Assistant Plugin
An Open Assistant plugin is defined by a single JSON file that adheres to a strict schema. This file contains metadata, authentication configurations, environmental variables, and the structural definition of the API endpoints.
The Top-Level Schema
The root of the JSON object establishes the identity of the plugin and defines how the assistant interacts with the target service:
id: A unique, URL-safe identifier used to namespace the generated tools (e.g.,azure_devops,toggl_tracker).display_name&description: Contextual metadata that helps the system and the user understand the purpose of the integration.base_url: The root URL for all API requests. This can include template variables to support multi-tenant or self-hosted instances.auth: The authentication strategy required to communicate with the API.config_fields: Variables provided by the user during installation (such as subdomains, organization IDs, or region codes) that parameterize the plugin.endpoints: An array of objects defining the individual API pathways that the assistant can invoke as tools.
From Endpoints to LLM Tools
Every entry in the endpoints array is automatically compiled into a tool named according to the pattern plugin_{id}_{endpoint.name}. The assistant's engine maps the endpoint's description directly to the tool's description, ensuring the LLM understands exactly when and why to invoke it.
Quick Start: A Declarative Task Management Plugin
The following example demonstrates a complete, functional plugin configuration for a mock task management API:
{
"id": "task_manager",
"display_name": "Task Manager",
"description": "Allows the assistant to retrieve, create, and update project tasks.",
"base_url": "https://api.taskmanager.example.com/v1",
"auth": {
"type": "header",
"header_name": "X-API-Key"
},
"config_fields": [],
"endpoints": [
{
"name": "list_tasks",
"display_name": "List Tasks",
"description": "Retrieve a list of tasks, optionally filtered by status and assignee.",
"method": "GET",
"path": "/tasks",
"parameters": [
{
"name": "status",
"in": "query",
"type": "string",
"description": "Filter tasks by status.",
"required": false
},
{
"name": "limit",
"in": "query",
"type": "integer",
"description": "The maximum number of tasks to return.",
"required": false,
"default": 20
}
]
}
]
}
When this plugin is loaded, the assistant instantly gains a tool named plugin_task_manager_list_tasks. When the user asks, "Show me my incomplete tasks," the LLM recognizes the tool, extracts the appropriate arguments, and the engine executes a secure HTTP request to the target API.
Authentication That Covers the Real World
In enterprise environments, simple, unauthenticated APIs are rare. Real-world APIs utilize diverse and often complex authentication mechanisms. Open Assistant's plugin engine natively supports these patterns directly within the JSON schema, eliminating the need for custom authentication wrappers.
1. Bearer Token Authentication
The standard Authorization: Bearer <token> pattern is configured by specifying the bearer type. The assistant prompts the user for their token upon installation and securely injects it into the headers of every outgoing request.
"auth": {
"type": "bearer"
}
2. Custom Header Authentication
For APIs that require credentials in non-standard headers (e.g., X-API-Key or X-Auth-Token), the header type allows developers to specify the exact target header name.
"auth": {
"type": "header",
"header_name": "X-API-Key"
}
3. HTTP Basic Authentication with Static Password
Certain legacy or specialized services (such as Toggl) utilize HTTP Basic Auth, where the user's API key acts as the username, and a static string (like "api_token") acts as the password. The basic auth type handles the Base64 encoding automatically.
"auth": {
"type": "basic",
"fixed_password": "api_token"
}
4. JWT Login with API Key Exchange (api_key_with_jwt)
The most complex real-world authentication pattern involves taking a static API key, exchanging it at a token endpoint to obtain a short-lived JSON Web Token (JWT), and using that JWT for subsequent resource requests.
Open Assistant handles this stateful flow entirely behind the scenes via the api_key_with_jwt authentication type.
"auth": {
"type": "api_key_with_jwt",
"api_key_header": "X-API-Key",
"token_endpoint": "https://auth.example.com/oauth/token",
"token_field": "access_token",
"token_prefix": "Bearer "
}
How the Engine Processes api_key_with_jwt
- Key Storage: The user provides their static API key when installing the plugin.
- Token Exchange: Prior to executing an endpoint request, the engine checks its in-memory cache for a valid JWT. If none exists (or if it has expired), the engine sends a request to the
token_endpoint, passing the user's API key in the header specified byapi_key_header. - Response Parsing: The engine extracts the JWT from the JSON response using the key defined in
token_field. - Injection: The engine injects the token (prefixed by
token_prefix) into theAuthorizationheader of the actual API call.
This multi-step, stateful process is executed entirely by the Open Assistant runtime. The developer only needs to write five lines of declarative JSON configuration.
Config Fields for Multi-Tenant APIs
A common challenge when designing reusable integrations is supporting multi-tenancy. For instance, self-hosted services (like Jira Server or custom GitLab instances) or multi-tenant cloud platforms (like Azure DevOps) require different base URLs or organization identifiers for different users.
To solve this, Open Assistant introduces config_fields. These fields define non-secret, user-specific parameters gathered during plugin installation. These parameters are then dynamically substituted into the base_url or endpoint paths at runtime.
Case Study: Azure DevOps Integration
Azure DevOps structures its API endpoints around specific organization names: https://dev.azure.com/{organization}. The following schema demonstrates how config_fields allow a single plugin definition to serve any Azure DevOps user.
{
"id": "azure_devops",
"display_name": "Azure DevOps",
"description": "Manage work items and pipelines in Azure DevOps.",
"base_url": "https://dev.azure.com/{organization}",
"auth": {
"type": "bearer"
},
"config_fields": [
{
"key": "organization",
"display_name": "Organization Name",
"description": "The name of your Azure DevOps organization.",
"required": true,
"sensitive": false,
"placeholder": "my-enterprise-org"
}
],
"endpoints": [
{
"name": "list_projects",
"display_name": "List Projects",
"description": "Retrieve all projects within the organization.",
"method": "GET",
"path": "/_apis/projects?api-version=7.0",
"parameters": []
}
]
}
The Substitution Mechanism
When a user installs this plugin:
- The Open Assistant user interface displays a form requesting the "Organization Name" (using the provided description and placeholder).
- The input value (e.g.,
acme-corp) is securely stored in the user's plugin configuration. - At runtime, when the assistant calls the
list_projectstool, the engine detects the{organization}placeholder in thebase_urland replaces it withacme-corp, resulting in an execution target of:https://dev.azure.com/acme-corp/_apis/projects?api-version=7.0
Parameters and Placement
For the LLM to interact effectively with an API, it must understand how to construct the requests. The parameters array within each endpoint definition acts as the translation guide, mapping the arguments generated by the LLM to their correct locations in the HTTP request.
Each parameter specifies its placement using the in property, which supports four primary locations:
Parameter Location (in) |
Description | Example Target |
|---|---|---|
path |
Dynamic variables embedded directly within the URL path. | /projects/{project_id}/issues |
query |
Key-value pairs appended to the end of the URL. | /issues?status=open&priority=high |
body |
Fields compiled into a structured JSON payload for write operations. | POST payload: {"title": "Bug report"} |
header |
Custom metadata headers sent with the request. | X-Project-Context: 12345 |
Parameter Mapping in Action
Consider an invoicing service endpoint designed to retrieve transactions within a specific date range:
{
"name": "get_invoices",
"display_name": "Get Invoices",
"description": "Retrieve invoices filtered by date range and payment status.",
"method": "GET",
"path": "/invoices",
"parameters": [
{
"name": "start_date",
"in": "query",
"type": "string",
"description": "The start date in YYYY-MM-DD format.",
"required": true
},
{
"name": "status",
"in": "query",
"type": "string",
"description": "Filter by payment status: paid, unpaid, or overdue.",
"required": false,
"default": "unpaid"
}
]
}
When a user asks: "Show me my unpaid invoices from last month," the LLM computes the date range, matches the arguments to the parameter schema, and the engine constructs and executes the request automatically.
Validation and Safety
Exposing raw APIs to intelligent language models requires strict guardrails. Without validation, malformed tool calls can result in server-side errors, rate-limiting, or unexpected state changes in connected systems.
Open Assistant implements a two-tier validation layer to ensure safety and system stability.
1. Schema Validation (Install-Time)
Every plugin configuration must conform to the official JSON Schema located at src/plugins/plugin_schema.json. When a developer or administrator attempts to register a new plugin (either by placing it in data/plugins/ or loading it via the settings user interface), the server validates the file.
If the JSON violates the schema, the server rejects the installation with a 422 Unprocessable Entity error. This feedback loop ensures that invalid configurations never reach the runtime environment.
2. Runtime Parameter Validation
Before executing an HTTP request, the Open Assistant engine validates the arguments generated by the LLM against the parameter definitions in the plugin schema:
- Type Safety: If a parameter is defined as an
integer, but the LLM generates a string representation, the engine attempts a safe coercion or raises an immediate execution error, preventing a malformed payload from reaching the target API. - Requirement Checks: If a parameter marked as
required: trueis missing from the LLM's tool call, the engine halts execution and instructs the model to provide the missing argument, saving network bandwidth and preventing API-side validation failures.
The Bigger Picture: The JSON IS the MCP Server
The shift from imperative integration code to declarative configuration represents a paradigm shift in how we connect AI systems to the web.
When developers build a traditional MCP server, they spend significant time writing code that acts as a translator: mapping incoming JSON-RPC calls from the client to HTTP requests, and translating the responses back. They are writing code to do what a structured configuration can describe.
By utilizing Open Assistant's plugin system, the JSON file effectively becomes the MCP server.
This declarative model offers several key architectural advantages:
- Portability: Because a plugin is defined entirely in a standard JSON format, it can be easily versioned in git repositories, shared across different instances of Open Assistant, and distributed without worrying about runtime dependencies or environment discrepancies.
- Maintainability: Updating an integration to support a new API endpoint no longer requires redeploying a microservice. It simply requires adding a new block to the
endpointsarray in the JSON file. - Security: Credentials and API keys never touch third-party adapter servers. They are managed directly by the Open Assistant core instance, reducing the number of systems that handle sensitive access tokens.
Get Started
Integrating your services with Open Assistant is straightforward. You can begin building and testing your own declarative plugins today:
- Launch an Instance: Visit platform.open-assistant.org to deploy and configure your dedicated Open Assistant environment.
- Explore the Platform: Review the core architecture, deployment guides, and capability overviews at open-assistant.org.
- Install a Plugin: Navigating to the Settings → Plugins tab inside your instance allows you to upload custom JSON schemas or enable built-in integrations instantly.
By eliminating the need for dedicated adapter servers, the Open Assistant plugin system simplifies the process of connecting AI models to your data. Write the schema, define your endpoints, and let the assistant manage the rest.
