feat: OpenCode Integration - Plugin + MCP Support #307

Closed
opened 2026-01-25 18:57:10 +00:00 by jack · 0 comments
Owner

Zusammenfassung

Integration von remembr in OpenCode, den Go-basierten AI Coding Agent mit TypeScript Plugin-System.


Über OpenCode

OpenCode ist ein Open-Source AI Coding Agent für das Terminal:

  • Sprache: Go (Core) + TypeScript (Plugins)
  • Features: Multi-Model, LSP Integration, MCP Support
  • Plugin-System: Hooks für tool.execute.before/after
  • Repository: https://github.com/opencode-ai/opencode

Integrationsmöglichkeiten

Option A: MCP Server (Minimal)

Unser bestehendes HTTP/SSE Backend als MCP-Endpunkt exponieren.

Konfiguration in OpenCode:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "remembr": {
      "type": "remote",
      "url": "http://localhost:37777/mcp",
      "headers": {
        "Authorization": "Bearer {env:REMEMBR_API_KEY}"
      }
    }
  }
}

Vorteile:

  • Minimaler Aufwand
  • Funktioniert sofort mit bestehendem Backend
  • Keine OpenCode-spezifische Codebase nötig

Nachteile:

  • Keine automatische Observation-Extraktion
  • User muss manuell memory_search aufrufen

Option B: Native Plugin (Empfohlen)

TypeScript Plugin mit Hook-Integration für automatische Memory-Erfassung.

Plugin-Struktur:

packages/opencode-plugin/
├── src/
│   ├── index.ts          # Plugin Entry
│   ├── hooks.ts          # tool.execute.before/after
│   ├── tools.ts          # Custom MCP Tools
│   ├── config.ts         # Plugin Configuration
│   └── api-client.ts     # remembr Backend Client
├── package.json
└── tsconfig.json

Plugin-Implementierung

Entry Point

// packages/opencode-plugin/src/index.ts
import type { Plugin } from "@opencode-ai/plugin"
import { createHooks } from "./hooks"
import { createTools } from "./tools"
import { loadConfig } from "./config"

export const RemembrPlugin: Plugin = async (ctx) => {
  const config = await loadConfig()
  
  return {
    ...createHooks(config),
    tool: createTools(config),
  }
}

export default RemembrPlugin

Hook-System

// packages/opencode-plugin/src/hooks.ts
import type { PluginHooks } from "@opencode-ai/plugin"
import { RemembrClient } from "./api-client"

export function createHooks(config: Config): PluginHooks {
  const client = new RemembrClient(config.backendUrl)
  let sessionId: string | null = null

  return {
    // Session-Start Hook
    "session.start": async (input) => {
      sessionId = await client.startSession({
        project: input.projectName,
        workingDirectory: input.cwd,
      })
    },

    // Vor Tool-Ausführung: Context injizieren
    "tool.execute.before": async (input, output) => {
      // Relevante Memories für aktuellen Context suchen
      if (input.tool === "edit" || input.tool === "write") {
        const memories = await client.search({
          query: output.args.filePath,
          limit: 3,
        })
        
        if (memories.length > 0) {
          // Context als System-Message injizieren
          return {
            ...output,
            context: memories.map(m => m.text).join("\n"),
          }
        }
      }
    },

    // Nach Tool-Ausführung: Observations extrahieren
    "tool.execute.after": async (input, result) => {
      // Nur bei erfolgreichen Edits
      if (input.tool === "edit" && !result.error) {
        await client.queueObservation({
          sessionId,
          toolName: input.tool,
          toolInput: input.args,
          toolOutput: result.content,
        })
      }

      // Bash-Commands loggen
      if (input.tool === "bash") {
        await client.logCommand({
          sessionId,
          command: input.args.command,
          exitCode: result.exitCode,
        })
      }
    },

    // Session-Ende Hook
    "session.end": async () => {
      if (sessionId) {
        await client.endSession(sessionId)
        sessionId = null
      }
    },
  }
}

Custom Tools

// packages/opencode-plugin/src/tools.ts
import { tool } from "@opencode-ai/plugin"
import { RemembrClient } from "./api-client"

export function createTools(config: Config) {
  const client = new RemembrClient(config.backendUrl)

  return {
    memory_search: tool({
      description: "Search remembr memories using semantic search",
      args: {
        query: tool.schema.string().describe("Search query"),
        limit: tool.schema.number().default(10),
        type: tool.schema.enum([
          "decision", "bugfix", "feature", "discovery"
        ]).optional(),
      },
      async execute(args) {
        const results = await client.search(args)
        return JSON.stringify(results, null, 2)
      },
    }),

    memory_save: tool({
      description: "Save an important observation to remembr",
      args: {
        text: tool.schema.string().describe("Memory content"),
        type: tool.schema.enum([
          "decision", "discovery", "note", "bookmark"
        ]).default("note"),
        title: tool.schema.string().optional(),
      },
      async execute(args) {
        const result = await client.saveMemory(args)
        return `Memory saved: ${result.id}`
      },
    }),

    memory_timeline: tool({
      description: "Get context around a specific memory",
      args: {
        anchor: tool.schema.number().describe("Memory ID"),
        depth_before: tool.schema.number().default(3),
        depth_after: tool.schema.number().default(3),
      },
      async execute(args) {
        const timeline = await client.timeline(args)
        return JSON.stringify(timeline, null, 2)
      },
    }),
  }
}

API Client

// packages/opencode-plugin/src/api-client.ts
export class RemembrClient {
  constructor(private baseUrl: string) {}

  async startSession(params: SessionParams): Promise<string> {
    const res = await fetch(`${this.baseUrl}/api/sessions`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(params),
    })
    const data = await res.json()
    return data.sessionId
  }

  async search(params: SearchParams): Promise<Memory[]> {
    const res = await fetch(`${this.baseUrl}/api/search`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(params),
    })
    return res.json()
  }

  async saveMemory(params: SaveParams): Promise<{ id: number }> {
    const res = await fetch(`${this.baseUrl}/api/observations`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(params),
    })
    return res.json()
  }

  // ... weitere Methoden
}

MCP Endpoint (Backend-Erweiterung)

Für Option A brauchen wir einen MCP-kompatiblen Endpoint:

// packages/backend/src/routes/mcp.ts
export class MCPRouter extends BaseRouter {
  protected setupRoutes(): void {
    // MCP Tool Discovery
    this.router.get("/tools", this.listTools.bind(this))
    
    // MCP Tool Execution
    this.router.post("/tools/:name", this.executeTool.bind(this))
  }

  private async listTools(req: Request, res: Response) {
    this.success(res, {
      tools: [
        {
          name: "search",
          description: "Search memories using semantic search",
          inputSchema: { /* JSON Schema */ }
        },
        {
          name: "save_memory",
          description: "Save a new memory",
          inputSchema: { /* JSON Schema */ }
        },
        {
          name: "timeline",
          description: "Get context around a memory",
          inputSchema: { /* JSON Schema */ }
        },
      ]
    })
  }
}

Konkurrenzanalyse: OpenCode Memory

Es existiert bereits OpenCode Memory:

Feature OpenCode Memory remembr
Provider OpenCode only Provider-agnostic
Backend Lokal (SQLite) HTTP/SSE Server
Vector Search Lokal Qdrant
Endless Mode
WebUI Basic Vollständig
Insights
Multi-Device (geplant)

Differenzierung: remembr bietet mehr Features und ist plattformübergreifend nutzbar.


Implementierungs-Roadmap

Phase 1: MCP Endpoint

  • /api/mcp/tools - Tool Discovery
  • /api/mcp/tools/:name - Tool Execution
  • MCP-kompatibles Response-Format
  • Dokumentation für OpenCode-Config

Phase 2: Native Plugin

  • Plugin-Package erstellen
  • Hook-Integration implementieren
  • Custom Tools implementieren
  • API Client für Backend

Phase 3: Publishing

  • npm Package veröffentlichen
  • OpenCode Plugin Registry Eintrag
  • Dokumentation auf remembr Website

Aufwand-Schätzung

Task Aufwand
MCP Endpoint (Backend) 6-8h
Plugin Grundstruktur 4-6h
Hook-Integration 6-8h
Custom Tools 4-6h
API Client 3-4h
Testing 4-6h
Dokumentation 3-4h
Gesamt 30-42h

Akzeptanzkriterien

  • MCP Endpoint funktioniert mit OpenCode-Config
  • Plugin installierbar via npm
  • Automatische Session-Erfassung funktioniert
  • Semantic Search über Plugin verfügbar
  • Observations werden extrahiert und gespeichert
  • Dokumentation vollständig

Referenzen

## Zusammenfassung Integration von remembr in [OpenCode](https://opencode.ai/), den Go-basierten AI Coding Agent mit TypeScript Plugin-System. --- ## Über OpenCode OpenCode ist ein Open-Source AI Coding Agent für das Terminal: - **Sprache:** Go (Core) + TypeScript (Plugins) - **Features:** Multi-Model, LSP Integration, MCP Support - **Plugin-System:** Hooks für `tool.execute.before/after` - **Repository:** https://github.com/opencode-ai/opencode --- ## Integrationsmöglichkeiten ### Option A: MCP Server (Minimal) Unser bestehendes HTTP/SSE Backend als MCP-Endpunkt exponieren. **Konfiguration in OpenCode:** ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "remembr": { "type": "remote", "url": "http://localhost:37777/mcp", "headers": { "Authorization": "Bearer {env:REMEMBR_API_KEY}" } } } } ``` **Vorteile:** - Minimaler Aufwand - Funktioniert sofort mit bestehendem Backend - Keine OpenCode-spezifische Codebase nötig **Nachteile:** - Keine automatische Observation-Extraktion - User muss manuell `memory_search` aufrufen ### Option B: Native Plugin (Empfohlen) TypeScript Plugin mit Hook-Integration für automatische Memory-Erfassung. **Plugin-Struktur:** ``` packages/opencode-plugin/ ├── src/ │ ├── index.ts # Plugin Entry │ ├── hooks.ts # tool.execute.before/after │ ├── tools.ts # Custom MCP Tools │ ├── config.ts # Plugin Configuration │ └── api-client.ts # remembr Backend Client ├── package.json └── tsconfig.json ``` --- ## Plugin-Implementierung ### Entry Point ```typescript // packages/opencode-plugin/src/index.ts import type { Plugin } from "@opencode-ai/plugin" import { createHooks } from "./hooks" import { createTools } from "./tools" import { loadConfig } from "./config" export const RemembrPlugin: Plugin = async (ctx) => { const config = await loadConfig() return { ...createHooks(config), tool: createTools(config), } } export default RemembrPlugin ``` ### Hook-System ```typescript // packages/opencode-plugin/src/hooks.ts import type { PluginHooks } from "@opencode-ai/plugin" import { RemembrClient } from "./api-client" export function createHooks(config: Config): PluginHooks { const client = new RemembrClient(config.backendUrl) let sessionId: string | null = null return { // Session-Start Hook "session.start": async (input) => { sessionId = await client.startSession({ project: input.projectName, workingDirectory: input.cwd, }) }, // Vor Tool-Ausführung: Context injizieren "tool.execute.before": async (input, output) => { // Relevante Memories für aktuellen Context suchen if (input.tool === "edit" || input.tool === "write") { const memories = await client.search({ query: output.args.filePath, limit: 3, }) if (memories.length > 0) { // Context als System-Message injizieren return { ...output, context: memories.map(m => m.text).join("\n"), } } } }, // Nach Tool-Ausführung: Observations extrahieren "tool.execute.after": async (input, result) => { // Nur bei erfolgreichen Edits if (input.tool === "edit" && !result.error) { await client.queueObservation({ sessionId, toolName: input.tool, toolInput: input.args, toolOutput: result.content, }) } // Bash-Commands loggen if (input.tool === "bash") { await client.logCommand({ sessionId, command: input.args.command, exitCode: result.exitCode, }) } }, // Session-Ende Hook "session.end": async () => { if (sessionId) { await client.endSession(sessionId) sessionId = null } }, } } ``` ### Custom Tools ```typescript // packages/opencode-plugin/src/tools.ts import { tool } from "@opencode-ai/plugin" import { RemembrClient } from "./api-client" export function createTools(config: Config) { const client = new RemembrClient(config.backendUrl) return { memory_search: tool({ description: "Search remembr memories using semantic search", args: { query: tool.schema.string().describe("Search query"), limit: tool.schema.number().default(10), type: tool.schema.enum([ "decision", "bugfix", "feature", "discovery" ]).optional(), }, async execute(args) { const results = await client.search(args) return JSON.stringify(results, null, 2) }, }), memory_save: tool({ description: "Save an important observation to remembr", args: { text: tool.schema.string().describe("Memory content"), type: tool.schema.enum([ "decision", "discovery", "note", "bookmark" ]).default("note"), title: tool.schema.string().optional(), }, async execute(args) { const result = await client.saveMemory(args) return `Memory saved: ${result.id}` }, }), memory_timeline: tool({ description: "Get context around a specific memory", args: { anchor: tool.schema.number().describe("Memory ID"), depth_before: tool.schema.number().default(3), depth_after: tool.schema.number().default(3), }, async execute(args) { const timeline = await client.timeline(args) return JSON.stringify(timeline, null, 2) }, }), } } ``` ### API Client ```typescript // packages/opencode-plugin/src/api-client.ts export class RemembrClient { constructor(private baseUrl: string) {} async startSession(params: SessionParams): Promise<string> { const res = await fetch(`${this.baseUrl}/api/sessions`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params), }) const data = await res.json() return data.sessionId } async search(params: SearchParams): Promise<Memory[]> { const res = await fetch(`${this.baseUrl}/api/search`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params), }) return res.json() } async saveMemory(params: SaveParams): Promise<{ id: number }> { const res = await fetch(`${this.baseUrl}/api/observations`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params), }) return res.json() } // ... weitere Methoden } ``` --- ## MCP Endpoint (Backend-Erweiterung) Für Option A brauchen wir einen MCP-kompatiblen Endpoint: ```typescript // packages/backend/src/routes/mcp.ts export class MCPRouter extends BaseRouter { protected setupRoutes(): void { // MCP Tool Discovery this.router.get("/tools", this.listTools.bind(this)) // MCP Tool Execution this.router.post("/tools/:name", this.executeTool.bind(this)) } private async listTools(req: Request, res: Response) { this.success(res, { tools: [ { name: "search", description: "Search memories using semantic search", inputSchema: { /* JSON Schema */ } }, { name: "save_memory", description: "Save a new memory", inputSchema: { /* JSON Schema */ } }, { name: "timeline", description: "Get context around a memory", inputSchema: { /* JSON Schema */ } }, ] }) } } ``` --- ## Konkurrenzanalyse: OpenCode Memory Es existiert bereits [OpenCode Memory](https://github.com/tickernelz/opencode-mem): | Feature | OpenCode Memory | remembr | |---------|-----------------|---------| | **Provider** | OpenCode only | Provider-agnostic | | **Backend** | Lokal (SQLite) | HTTP/SSE Server | | **Vector Search** | Lokal | Qdrant | | **Endless Mode** | ❌ | ✅ | | **WebUI** | Basic | Vollständig | | **Insights** | ❌ | ✅ | | **Multi-Device** | ❌ | ✅ (geplant) | **Differenzierung:** remembr bietet mehr Features und ist plattformübergreifend nutzbar. --- ## Implementierungs-Roadmap ### Phase 1: MCP Endpoint - [ ] `/api/mcp/tools` - Tool Discovery - [ ] `/api/mcp/tools/:name` - Tool Execution - [ ] MCP-kompatibles Response-Format - [ ] Dokumentation für OpenCode-Config ### Phase 2: Native Plugin - [ ] Plugin-Package erstellen - [ ] Hook-Integration implementieren - [ ] Custom Tools implementieren - [ ] API Client für Backend ### Phase 3: Publishing - [ ] npm Package veröffentlichen - [ ] OpenCode Plugin Registry Eintrag - [ ] Dokumentation auf remembr Website --- ## Aufwand-Schätzung | Task | Aufwand | |------|---------| | MCP Endpoint (Backend) | 6-8h | | Plugin Grundstruktur | 4-6h | | Hook-Integration | 6-8h | | Custom Tools | 4-6h | | API Client | 3-4h | | Testing | 4-6h | | Dokumentation | 3-4h | | **Gesamt** | **30-42h** | --- ## Akzeptanzkriterien - [ ] MCP Endpoint funktioniert mit OpenCode-Config - [ ] Plugin installierbar via npm - [ ] Automatische Session-Erfassung funktioniert - [ ] Semantic Search über Plugin verfügbar - [ ] Observations werden extrahiert und gespeichert - [ ] Dokumentation vollständig --- ## Referenzen - [OpenCode GitHub](https://github.com/opencode-ai/opencode) - [OpenCode Plugin Docs](https://opencode.ai/docs/plugins/) - [OpenCode MCP Docs](https://opencode.ai/docs/mcp-servers/) - [MCP Specification](https://modelcontextprotocol.io/) - [OpenCode Memory](https://github.com/tickernelz/opencode-mem)
jack closed this issue 2026-01-25 20:11:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
customable/claude-mem#307
No description provided.