Agent Plugins

Give Spektral's AI agent new capabilities: create Jira issues, search Linear, open GitHub PRs, or route generation through your own AI provider: by installing plugins. A plugin can be a Model Context Protocol (MCP) server that advertises tools, an AI provider adapter, or both.

How It Works

Each plugin lives in its own directory under ~/.ds-creator/plugins/. On startup, Spektral discovers every plugin, spawns enabled ones as stdio MCP servers, calls listTools, and exposes each tool to the agent namespaced as pluginId__toolName.

When the agent decides to call one of those tools, Spektral routes the request to the right plugin process over stdio and streams the result back. Plugins run as separate OS processes, so a broken plugin can't crash the app or read its memory.

Spektral (Electron)              Plugin process (stdio)
Agent: "call jira__create_issue"
   ↓
PluginManager -> spawn node server.mjs -> MCP Server
   ← list of tools, call results ←         tools: create_issue, search

Plugin AI Providers

Plugins can also add entries to Settings → AI Provider. HTTP providers declare an Anthropic- or OpenAI-compatible endpoint plus headers. Command providers get the complete Anthropic-shaped request on stdin and stream Anthropic SSE on stdout, which lets the plugin own anything provider-specific: AWS SigV4 signing, local credential refresh, or running saml2aws before calling Bedrock.

Provider typeUse it whenWhat Spektral does
anthropicYour endpoint accepts Anthropic Messages API requests and returns Anthropic SSE.Passes the agent request through, adds configured headers, and streams the response directly to the app.
openaiYour endpoint is OpenAI chat-completions compatible.Translates Spektral's Anthropic-shaped request into OpenAI format, then converts OpenAI SSE back into Anthropic SSE.
commandYou need custom auth, request signing, credential refresh, model routing, or a provider API that is not wire-compatible.Starts your command, writes one JSON envelope to stdin, forwards stdout as the model stream, and reports stderr on failure.
// plugin.json
{
  "id": "aws-bedrock",
  "name": "AWS Bedrock",
  "aiProviders": [
    {
      "id": "claude",
      "name": "Bedrock Claude",
      "type": "command",
      "defaultModel": "anthropic.claude-sonnet-4-5-20250929-v1:0",
      "models": ["anthropic.claude-sonnet-4-5-20250929-v1:0"],
      "request": {
        "command": "node",
        "args": ["bedrock-provider.mjs"],
        "env": {
          "AWS_PROFILE": "${config.profile}",
          "AWS_REGION": "${config.region}"
        }
      }
    }
  ],
  "config": [
    { "key": "profile", "label": "AWS profile", "type": "string", "default": "default" },
    { "key": "region", "label": "AWS region", "type": "string", "default": "us-east-1" }
  ]
}

The command receives JSON shaped as { protocol, provider, config, request }. request is the exact Messages API-style payload Spektral would have sent to Anthropic, including tools and conversation history.

Command Provider Contract

A command provider is intentionally small: Spektral launches it per generation, writes a single JSON object to stdin, and expects an Anthropic-compatible server-sent event stream on stdout. The process can do anything it needs before the first token: check AWS credentials, run an SSO helper, mint a temporary token, or call an internal gateway.

// stdin
{
  "protocol": "spektral.ai-provider.v1",
  "provider": {
    "id": "plugin:aws-bedrock:claude",
    "pluginId": "aws-bedrock",
    "pluginProviderId": "claude",
    "type": "command"
  },
  "config": { "profile": "default", "region": "us-east-1" },
  "request": {
    "model": "anthropic.claude-sonnet-4-5-20250929-v1:0",
    "max_tokens": 16384,
    "stream": true,
    "system": "...",
    "messages": [],
    "tools": []
  }
}

Stdout should contain only SSE chunks. Log diagnostics to stderr. If the process exits non-zero, Spektral shows stderr as the provider error.

# stdout
event: message_start
data: {"type":"message_start","message":{"id":"msg_...","type":"message","role":"assistant","content":[]}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}

event: message_stop
data: {"type":"message_stop"}

Bedrock With saml2aws

Bedrock is a good fit for type: "command" because authentication usually happens outside a simple bearer-token header. Your adapter can check whether the configured AWS profile has valid temporary credentials, run saml2aws login --profile <profile> when it needs to refresh them, sign the Bedrock request with the AWS SDK, and translate Bedrock's response stream into Anthropic SSE.

// bedrock-provider.mjs sketch
const input = JSON.parse(await new Response(process.stdin).text());

await ensureAwsSession(input.config.profile); // may run: saml2aws login --profile ...

const bedrockRequest = toBedrockConverseStream(input.request);
const stream = await signedBedrockCall({
  region: input.config.region,
  profile: input.config.profile,
  modelId: input.request.model,
  body: bedrockRequest,
});

for await (const event of stream) {
  process.stdout.write(toAnthropicSse(event));
}

Keep the plugin responsible for provider-specific policy: profile names, role selection, token caches, guardrails, corporate proxy settings, and model allowlists. Spektral only needs the selected provider id and model id.

Installing a Plugin

You don't have to write code to add a plugin: if someone has already packaged one as a zip, just import it from Settings → Plugins → Install .zip. Spektral extracts the archive, validates its plugin.json, and drops the plugin into ~/.ds-creator/plugins/<id>/. If a plugin with the same id already exists it is replaced atomically.

The archive must contain a plugin.json manifest either at the root or one level deep inside a single wrapping directory. Anything else is rejected with a clear error.

Prefer the manual route? Drop the plugin folder directly into ~/.ds-creator/plugins/ and click Rescan. Either way, flip the Enabled toggle to start the MCP server and expose its tools to the agent.

If the plugin contains a package.json, Spektral shows an Install deps button. It runs npm install --include=optional inside that plugin directory before you enable or reconnect the server.

Plugin Marketplaces

Spektral can install plugins from JSON marketplace feeds hosted as static files, including the default Cloudflare-hosted feed at /marketplace/plugins.json. Client-specific feeds can live on their own Cloudflare Pages site and be added from Settings → Plugins → Marketplace.

A marketplace entry only needs enough information to present and download the plugin. The zip's own plugin.json remains the source of truth for launch commands and full config fields.

{
  "name": "Acme Plugin Marketplace",
  "generatedAt": "2026-05-29T16:54:07.590Z",
  "plugins": [
    {
      "id": "jira",
      "name": "Jira",
      "version": "1.0.0",
      "description": "Search, create, and update Jira issues.",
      "downloadUrl": "downloads/jira.zip",
      "repoUrl": "https://github.com/acme/plugins/tree/main/plugins/jira",
      "configKeys": [
        { "key": "apiToken", "type": "password", "required": true, "secret": true }
      ]
    }
  ]
}

Required per plugin: id, name, and downloadUrl. Recommended: version, description, and repoUrl. Optional metadata: archetype, agents, and configKeys.

Quickstart: Build a Plugin

The shortest possible plugin exposes one tool that echoes its input. You'll build it in four small steps.

1

Create the Plugin Directory

The directory name doesn't matter: the id field in your manifest is what Spektral uses to namespace tools.

$ mkdir -p ~/.ds-creator/plugins/echo
$ cd ~/.ds-creator/plugins/echo
$ npm init -y
$ npm install @modelcontextprotocol/sdk
2

Write plugin.json

This manifest tells Spektral how to launch your server and what user-facing config fields to render in the Plugins settings panel.

// ~/.ds-creator/plugins/echo/plugin.json
{
  "id": "echo",
  "name": "Echo",
  "version": "0.1.0",
  "description": "Echoes its input back with a configurable prefix.",
  "server": {
    "command": "node",
    "args": ["server.mjs"],
    "env": {
      "ECHO_PREFIX": "${config.prefix}"
    }
  },
  "config": [
    {
      "key": "prefix",
      "label": "Reply prefix",
      "type": "string",
      "default": "echo:"
    }
  ]
}

Notice "${config.prefix}": Spektral substitutes config values into env before spawning the process, so your server just reads process.env.ECHO_PREFIX.

3

Write the MCP Server

The MCP SDK handles the protocol plumbing. You only need to declare tools and handle tool calls.

// ~/.ds-creator/plugins/echo/server.mjs
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

const PREFIX = process.env.ECHO_PREFIX || 'echo:';

const server = new Server(
  { name: 'echo', version: '0.1.0' },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'echo',
    description: 'Echo a message back with the configured prefix.',
    inputSchema: {
      type: 'object',
      properties: {
        message: { type: 'string', description: 'Message to echo' },
      },
      required: ['message'],
    },
  }],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => ({
  content: [{ type: 'text', text: `${PREFIX} ${req.params.arguments.message}` }],
}));

await server.connect(new StdioServerTransport());
4

Rescan, Configure, and Enable

Open Settings → Plugins in Spektral and click Rescan. Your Echo plugin appears in the list. Enter a prefix, click Save config, then flip the Enabled toggle.

Once connected, the tool shows up as echo__echo in the plugin's tool list. Start a new generation or iteration and ask the agent something like "use the echo tool to say hello": it will invoke your plugin and stream the result back into the conversation.

Manifest Reference

FieldTypeDescription
idstring (required)Stable unique identifier. Used to namespace tools (id__toolName) and persist config.
namestring (required)Human-readable name shown in the Plugins settings panel.
versionstringSemver string displayed next to the plugin name.
descriptionstringOne-line summary of what the plugin does.
server.commandstring (required for MCP tools)Executable that starts the MCP server (e.g. node, npx, python). AI-provider-only plugins can omit server.
server.argsstring[]Arguments passed to the command. Resolved relative to the plugin directory.
server.envRecord<string, string>Environment variables. Supports ${config.key} substitution.
server.cwdstringOptional working directory override. Defaults to the plugin directory.
aiProviders[]arrayOptional AI provider declarations. Each provider has id, name, type (anthropic, openai, or command), optional models, and either HTTP settings or request.command.
config[]arrayUser-editable fields rendered as a form in Settings. See below.

Config field schema

FieldTypeDescription
keystring (required)Lookup key for ${config.key} substitution.
labelstring (required)Human-readable label shown above the input.
type"string" | "password" | "number" | "boolean"Determines the input control. password renders a masked field (still stored in plaintext: see Security).
defaultstring | number | booleanValue shown until the user saves an override.
helpstringHelper text rendered below the input.

Reusing Existing MCP Servers

Because plugins are MCP servers, you can reuse any server from the growing MCP ecosystem: GitHub, Slack, filesystem, Postgres, and others. Drop a wrapper plugin.json in its own directory and point server at an npx invocation.

// ~/.ds-creator/plugins/github/plugin.json
{
  "id": "github",
  "name": "GitHub",
  "description": "Search issues, open PRs, read files.",
  "server": {
    "command": "npx",
    "args": ["-y", "@modelcontextprotocol/server-github"],
    "env": {
      "GITHUB_PERSONAL_ACCESS_TOKEN": "${config.token}"
    }
  },
  "config": [
    { "key": "token", "label": "Personal access token", "type": "password", "required": true }
  ]
}

Debugging

  • Anything your server writes to stderr is logged to the Spektral main process with the prefix [plugin:<id>]. Launch the app from a terminal to see it.
  • If a plugin fails to start, the error message is shown inline in the Plugins settings panel (connection status flips to Error). Check the logged stderr first.
  • After editing your server code, toggle the plugin off and on in Settings: Spektral restarts the process so your changes take effect.
  • Never write to stdout directly. MCP uses stdout for JSON-RPC framing; debug output belongs on stderr (console.error).

Security & Trust

Plugins run with the same permissions as Spektral itself: they can read your files, make network requests, and spend API quota on your behalf. Only install plugins from sources you trust, and review the command line in the manifest before enabling.

Config values, including those marked type: "password", are stored in plaintext in ~/.ds-creator/settings.json. Tokens you enter are only sent to your plugin's process, never uploaded anywhere by Spektral: but what the plugin does with them is up to the plugin.

Current Limits

The plugin system is intentionally minimal in its first release. Some things that are deferred:

  • Tool-only contributions. Plugins can't render their own UI panels or modals yet: capabilities are exposed through the agent's conversation.
  • No built-in OAuth flow. If your integration needs OAuth, the plugin process has to handle it directly. A first-class plugin OAuth helper is on the roadmap.
  • Per-plugin enable only. You can't approve individual tool calls yet; enabling a plugin trusts all its tools.