Building Custom Molecules
Molecules are discovery plugins that automatically sync data from external services into your SixDegree knowledge graph and provide MCP tools for AI agent interactions.
Overview
A molecule is a standalone process that:
- Discovers entities - Syncs data from external services (repositories, workflows, users, etc.)
- Creates relationships - Establishes connections between discovered entities
- Provides MCP tools - Gives AI agents capabilities to interact with external services
- Handles webhooks - Responds to real-time events from external services (optional)
Each molecule runs independently and communicates with the SixDegree platform via a standard protocol.
When to Build a Molecule
Build a custom molecule when you need to:
- Integrate a service not yet supported by existing molecules
- Discover custom internal resources or proprietary systems
- Implement specialized discovery logic for your domain
- Provide custom MCP tools for AI agent interactions
- Support a private or enterprise version of a service
Architecture
Dual Capabilities
Molecules support two distinct capabilities:
1. Discovery Capability
Discovers and syncs entities into your knowledge graph:
- Runs periodically (polling) or via webhooks
- Creates entities and relationships
- Updates when external data changes
- Targets a specific namespace
2. MCP Capability
Provides tools for AI agent interactions:
- Real-time operations (create issue, deploy, search)
- Read/write access to external services
- Available in chat interfaces
- Independent of discovery configuration
You can enable one or both capabilities per molecule.
Capability Design Principles
Understanding when to use Discovery vs MCP tools is critical for good molecule design.
Discovery (ontology): slowly-changing structural data
The ontology should contain structural entities that change infrequently and define the topology of your infrastructure:
- Organizations, projects, repositories, workspaces
- Users, groups, teams, roles
- Clusters, namespaces, services
- Channels, zones, policies
These entities are discovered periodically and cached. The AI agent queries the ontology to understand relationships and navigate the infrastructure graph.
Do NOT create MCP "list" tools for data that's already in the ontology. This creates redundancy and causes the AI to bypass the ontology's relationship graph.
MCP tools: dynamic data and actions
MCP tools should provide:
-
Live/Dynamic Data: Data that changes frequently and shouldn't be cached:
- Vulnerabilities, security issues
- Recent pipeline runs, workflow executions
- Active incidents, alerts
- Current metrics, logs
-
Actions: Operations that modify state:
- Trigger scans, runs, deployments
- Send messages, create tickets
- Approve/reject workflows
-
Deep Inspection: Detailed live data for specific entities:
get_projectwith live issue countsget_remediationadviceget_policyrules
Examples
| ❌ Redundant (Don't Do) | ✅ Appropriate |
|---|---|
list_projects | Discovery: Project entities in ontology |
list_channels | Discovery: Channel entities in ontology |
list_users | Discovery: User entities in ontology |
list_workspaces | Discovery: Workspace entities in ontology |
| (none) | list_pipelines: Recent/dynamic pipeline runs |
| (none) | search_vulnerabilities: Live vulnerability data |
| (none) | trigger_pipeline: Action |
| (none) | send_message: Action |
Why This Matters
When the AI uses a "list" tool instead of querying the ontology:
- Bypasses relationship traversal: The AI won't discover relationships like
SCANSorDEPLOYS_TOthat connect entities across tools - Breaks late tool disclosure: Tools are disclosed based on entity types discovered in the ontology. If the AI never traverses the ontology, relevant tools aren't disclosed
- Loses the graph: The AI gets a flat list instead of understanding the infrastructure topology
If the data is discovered into the ontology, don't provide a list tool for it. The ontology handles "what exists." MCP tools handle "what's happening now" and "do something."
Molecule SDK
The Molecule SDK provides the foundation for building molecules:
- Go SDK -
github.com/sixdegree-ai/molecule-sdk/go/molecule(production-ready) - Python SDK - Coming soon
- TypeScript SDK - Planned
Installation
go get github.com/sixdegree-ai/molecule-sdk/go/molecule
Quick Start
1. Create Module
mkdir my-molecule
cd my-molecule
go mod init github.com/yourorg/my-molecule
go get github.com/sixdegree-ai/molecule-sdk/go/molecule
2. Implement Molecule
package main
import (
"context"
"encoding/json"
"github.com/sixdegree-ai/molecule-sdk/go/molecule"
"go.uber.org/zap"
)
const version = "1.0.0"
func main() {
molecule.Run(&MyMolecule{})
}
type MyMolecule struct {
*molecule.BaseMolecule
config *molecule.MoleculeConfig
client *MyServiceClient
}
// Discovery configuration schema
var discoveryConfigSchema = json.RawMessage(`{
"type": "object",
"required": ["api_key"],
"properties": {
"api_key": {
"type": "string",
"title": "API Key",
"description": "Your service API key",
"x-order": 1,
"x-sixdegree": {"widget": "secret"}
},
"base_url": {
"type": "string",
"title": "Base URL",
"description": "Service base URL",
"x-order": 2,
"default": "https://api.myservice.com"
}
}
}`)
// MCP configuration schema
var mcpConfigSchema = json.RawMessage(`{
"type": "object",
"required": ["api_key"],
"properties": {
"api_key": {
"type": "string",
"title": "API Key",
"description": "API key for MCP tools",
"x-sixdegree": {"widget": "secret"}
}
}
}`)
// GetMetadata returns molecule metadata and capabilities
func (m *MyMolecule) GetMetadata(ctx context.Context) (*molecule.MoleculeMetadata, error) {
return &molecule.MoleculeMetadata{
Namespace: "molecules.sixdegree.ai",
Name: "my-molecule",
DisplayName: "My Service Integration",
Version: version,
Description: "Discovers resources from My Service",
Discovery: &molecule.DiscoveryCapability{
Supported: true,
Modes: []molecule.DiscoveryMode{molecule.DiscoveryModePolling},
ConfigSchema: discoveryConfigSchema,
EntityTypes: []molecule.EntityTypeDefinition{
{
Group: "entities.sixdegree.ai",
Version: "v1",
Kind: "MyResource",
Description: "A resource from My Service",
},
},
},
MCP: &molecule.MCPCapability{
Supported: true,
ConfigSchema: mcpConfigSchema,
},
}, nil
}
// Configure sets up the molecule with runtime configuration
func (m *MyMolecule) Configure(ctx context.Context, config *molecule.MoleculeConfig) error {
m.config = config
if config.Discovery != nil && config.Discovery.Enabled {
apiKey, err := molecule.RequireCapabilitySetting[string](config.Discovery, "api_key")
if err != nil {
return err
}
baseURL := molecule.GetCapabilitySetting(config.Discovery, "base_url", "https://api.myservice.com")
m.client = NewMyServiceClient(baseURL, apiKey)
}
return nil
}
// Discover performs discovery and streams entities
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100)
go func() {
defer rc.Close()
rc.SendProgress(0, "Starting discovery...")
resources, err := m.client.ListResources(ctx)
if err != nil {
rc.SendError("FETCH_FAILED", fmt.Sprintf("failed to list resources: %v", err))
return
}
for i, r := range resources {
entity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "MyResource").
Name(r.Name).
Namespace(m.config.Namespace).
Spec("url", r.URL).
Spec("status", r.Status).
Spec("created_at", r.CreatedAt).
Build()
rc.SendEntity(entity)
rc.SendProgress(((i+1)*100)/len(resources), fmt.Sprintf("Discovered %d/%d", i+1, len(resources)))
}
}()
return rc.Chan(), nil
}
3. Build and Test
go build -o my-molecule
# Test discovery
./my-molecule discover --config config.yaml --dry-run
# Run molecule server
./my-molecule serve --port 8080
Configuration Schemas
Configuration schemas define what users need to provide to configure your molecule. They use JSON Schema with SixDegree-specific extensions.
Schema Structure
var discoveryConfigSchema = json.RawMessage(`{
"type": "object",
"required": ["token"],
"properties": {
"token": {
"type": "string",
"title": "Access Token",
"description": "Service access token with required permissions",
"x-order": 1,
"x-sixdegree": {
"widget": "secret"
}
},
"organizations": {
"type": "array",
"title": "Organizations",
"description": "Organizations to discover",
"x-order": 2,
"items": {
"type": "string"
}
},
"include_archived": {
"type": "boolean",
"title": "Include Archived",
"description": "Include archived resources",
"x-order": 3,
"default": false
}
}
}`)
SixDegree Extensions
Use x-sixdegree for custom UI widgets:
| Widget | Description | Example Use Case |
|---|---|---|
secret | Password/token input (masked) | API keys, tokens |
org-picker | Organization selector | GitHub/GitLab orgs |
repo-picker | Repository selector | Specific repos |
multiselect | Multiple choice selector | Scopes, permissions |
Field Ordering
Use x-order to control field display order in the UI (lower numbers appear first).
Dynamic Configuration
For advanced use cases, support environment-specific or dynamic configuration:
func (m *MyMolecule) Initialize(ctx context.Context, config *molecule.MoleculeConfig, logger *zap.Logger) error {
// Load from config
baseURL := config.Discovery.Settings["base_url"].(string)
// Override with environment variables if present
if envURL := os.Getenv("MY_SERVICE_URL"); envURL != "" {
baseURL = envURL
}
m.client = NewClient(baseURL)
return nil
}
Molecule Interfaces
The SDK uses interface composition. Implement the interfaces that match your molecule's capabilities.
Core Interface
Every molecule must implement Molecule:
type Molecule interface {
// GetMetadata returns molecule identity and capabilities
GetMetadata(ctx context.Context) (*MoleculeMetadata, error)
// Configure sets up the molecule with runtime configuration
Configure(ctx context.Context, config *MoleculeConfig) error
// Shutdown gracefully stops the molecule
Shutdown(ctx context.Context) error
}
Discoverer
Implement if your molecule discovers entities and syncs them to the ontology:
type Discoverer interface {
Molecule
Discover(ctx context.Context, opts *DiscoveryOptions) (<-chan *DiscoveryResult, error)
}
ToolProvider
Implement if your molecule provides MCP tools for the AI agent:
type ToolProvider interface {
Molecule
ListTools(ctx context.Context) ([]ToolDefinition, error)
CallTool(ctx context.Context, req *ToolCallRequest) (*ToolCallResponse, error)
}
WebhookHandler
Optional. Implement alongside Discoverer for real-time event-driven discovery:
type WebhookHandler interface {
HandleWebhook(ctx context.Context, req *WebhookRequest) (*WebhookResponse, error)
}
VisualizationProvider
Implement if your molecule renders server-side visualizations for entity data:
type VisualizationProvider interface {
Molecule
ListVisualizations(ctx context.Context) ([]VisualizationDefinition, error)
RenderVisualization(ctx context.Context, req *VisualizationRenderRequest) (*VisualizationRenderResponse, error)
}
A molecule can implement any combination of these. Use FullMolecule as a convenience interface if you implement all of them.
Entities
Entities are the nodes in your knowledge graph. They follow a Kubernetes-inspired structure.
Entity Structure
type Entity struct {
APIVersion string // e.g., "entities.sixdegree.ai/v1"
Kind string // e.g., "GithubRepository"
Metadata EntityMetadata
Spec map[string]any // Entity-specific attributes
Status *EntityStatus // Optional lifecycle state
}
type EntityMetadata struct {
Name string
Namespace string
Labels map[string]string
Annotations map[string]string
}
Creating Entities
Use the EntityBuilder helper for fluent construction:
entity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "MyResource").
Name(r.Name).
Namespace(m.config.Namespace).
Label("language", "go").
Spec("url", r.URL).
Spec("status", r.Status).
Spec("created_at", r.CreatedAt.Format(time.RFC3339)).
Build()
Entity Naming
metadata.name is the stable identifier for an entity. It must be:
- Unique within its namespace and kind: no collisions
- Stable across discoveries: same resource = same name every run
- URL-path-safe: lowercase, hyphens, slashes; no spaces
// Good names
Name(repo.FullName) // e.g., "myorg/api-service"
Name(strconv.Itoa(issue.ID)) // e.g., "12345"
Name(user.Login) // e.g., "john-doe"
// Avoid
Name(fmt.Sprintf("resource_%d", time.Now().Unix())) // Not stable
Entity Kinds
Entity kinds define the type. Use PascalCase matching the Kind declared in GetMetadata:
// Good kind names
Kind: "GithubRepository"
Kind: "KubernetesDeployment"
Kind: "MyServiceWorkspace"
Entity Spec
The spec map stores entity-specific attributes:
Spec: map[string]any{
"url": "https://github.com/org/repo",
"language": "Go",
"stars": 42,
"private": false,
"created_at": time.Now().Format(time.RFC3339),
"topics": []string{"api", "golang"},
}
Spec guidelines:
- Use snake_case for keys
- Use ISO 8601 for timestamps
- Keep values as primitives or slices; avoid deeply nested objects
Relationships
Relationships connect entities to create the knowledge graph.
Relation Structure
type Relation struct {
Namespace string
RelationType string // e.g., "OWNS", "DEPLOYS_TO"
Source EntityReference
Target EntityReference
Labels map[string]string
Spec map[string]any // Optional relation attributes
Weight *float64 // Optional graph weight
}
Creating Relations
Use the RelationBuilder helper:
// Reference entities by coordinates
rel := molecule.NewRelationBuilder("DEPENDS_ON").
Namespace(m.config.Namespace).
From("entities.sixdegree.ai/v1", "MyService", m.config.Namespace, "api-service").
To("entities.sixdegree.ai/v1", "MyLibrary", m.config.Namespace, "logger").
Spec("version", "v1.2.3").
Build()
// Reference from existing entity pointers
rel := molecule.NewRelationBuilder("BELONGS_TO").
Namespace(m.config.Namespace).
FromEntity(projectEntity).
ToEntity(orgEntity).
Build()
Streaming Relations from Discover
Relations are streamed via the same result channel as entities:
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100)
go func() {
defer rc.Close()
for _, org := range orgs {
orgEntity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "MyOrg").
Name(org.Name).
Namespace(m.config.Namespace).
Build()
rc.SendEntity(orgEntity)
for _, project := range org.Projects {
projectEntity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "MyProject").
Name(project.Name).
Namespace(m.config.Namespace).
Spec("url", project.URL).
Build()
rc.SendEntity(projectEntity)
rel := molecule.NewRelationBuilder("BELONGS_TO").
Namespace(m.config.Namespace).
FromEntity(projectEntity).
ToEntity(orgEntity).
Build()
rc.SendRelation(rel)
}
}
}()
return rc.Chan(), nil
}
Relationship Types
Use clear, verb-based uppercase relationship types:
// Good relationship types
RelationType: "DEPENDS_ON"
RelationType: "DEPLOYS_TO"
RelationType: "CONTRIBUTES_TO"
RelationType: "OWNS"
RelationType: "SCANS"
// Avoid
RelationType: "related_to" // Too generic
RelationType: "has" // Unclear direction
Relationship Ownership
Relationships in SixDegree follow a clear ownership model:
1. Intra-Domain Relationships (Molecule Responsibility)
Molecules are fully responsible for creating relationships between entities within their own domain. These relationships are created directly during discovery:
// Snyk molecule creates SCANS relationships between its own entities
func (m *SnykMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100)
go func() {
defer rc.Close()
for _, project := range projects {
projectEntity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "SnykProject").
Name(project.ID).
Namespace(m.config.Namespace).
Spec("org_id", project.OrgID).
Build()
rc.SendEntity(projectEntity)
// Create relationship to the organization (both are Snyk entities)
rel := molecule.NewRelationBuilder("BELONGS_TO").
Namespace(m.config.Namespace).
From("entities.sixdegree.ai/v1", "SnykProject", m.config.Namespace, project.ID).
To("entities.sixdegree.ai/v1", "SnykOrganization", m.config.Namespace, project.OrgID).
Build()
rc.SendRelation(rel)
}
}()
return rc.Chan(), nil
}
Examples of intra-domain relationships:
SnykProject→BELONGS_TO→SnykOrganizationK8sDeployment→RUNS_IN→K8sNamespaceGitHubRepository→OWNED_BY→GitHubOrganization
2. Cross-Molecule Rules (Suggested by Molecules)
Molecules can suggest default rules for creating relationships to entities from other molecules. These rules are defined in the molecule metadata and run when entities are created:
func defaultRules() []molecule.RuleDefinition {
return []molecule.RuleDefinition{
{
Name: "snyk-project-to-github-repo",
Description: "Link Snyk projects to GitHub repositories they scan",
Enabled: true,
Priority: 100,
Trigger: molecule.RuleTrigger{
On: "entity.created",
Match: molecule.RuleTriggerMatch{
Kind: "SnykProject",
},
},
// Optional CEL expression for additional filtering
Condition: `entity.spec["origin"].startsWith("github.com/")`,
Actions: []molecule.RuleAction{
{
Type: "create_relation",
CreateRelation: &molecule.CreateRelationAction{
RelationType: "SCANS",
Target: &molecule.EntityRefTemplate{
Kind: "GithubRepository",
// Match the GitHub repo whose full_name matches the Snyk origin
MatchSpec: map[string]any{
"full_name": `{{ entity.spec["origin"] }}`,
},
},
},
},
},
},
}
}
Examples of cross-molecule rules:
SnykProject→SCANS→GithubRepository(based on origin URL)VaultAuthMethod(kubernetes type) →AUTHENTICATES→K8sCluster(based on cluster name in path)ArgoApplication→DEPLOYS→GithubRepository(based on source repo URL)
Cross-molecule rules allow the knowledge graph to connect entities across different tools automatically. The molecule that "knows" about the relationship (e.g., Snyk knows which repos it scans) suggests the rule.
3. User-Defined Rules
Users can create custom rules to define relationships specific to their environment. These are configured in the platform UI or via the API:
# Example user-defined rule
apiVersion: sixdegree.ai/v1
kind: Rule
metadata:
name: team-ownership
namespace: production
spec:
description: "Link repositories to teams based on CODEOWNERS"
trigger:
on: entity.created
match:
kind: GithubRepository
conditions:
- field: spec.codeowners
operator: contains
value: "@myorg/platform-team"
actions:
- type: create_relation
config:
relation_type: OWNED_BY
target_kind: Team
target_name: "Platform Team"
Use cases for user-defined rules:
- Custom ownership mappings (repos → teams)
- Environment-specific relationships (services → infrastructure)
- Business logic relationships (services → cost centers)
- Compliance mappings (resources → compliance frameworks)
Relationship Resolution Order
When resolving relationships, the platform applies rules in this order:
- Direct relationships: Created by molecules during discovery (highest priority)
- Molecule-suggested rules: Default rules from molecule metadata
- User-defined rules: Custom rules configured by users
This ensures that explicit relationships from molecules take precedence, while rules fill in the connections that require cross-molecule knowledge or user-specific logic.
Configuration
Implement Configure to receive runtime configuration from the platform:
func (m *MyMolecule) Configure(ctx context.Context, config *molecule.MoleculeConfig) error {
m.config = config
if molecule.IsCapabilityEnabled(config, molecule.CapabilityDiscovery) {
token, err := molecule.RequireCapabilitySetting[string](config.Discovery, "token")
if err != nil {
return err
}
m.discoveryClient = NewClient(token)
}
if molecule.IsCapabilityEnabled(config, molecule.CapabilityMCP) {
token, err := molecule.RequireCapabilitySetting[string](config.MCP, "token")
if err != nil {
return err
}
m.mcpClient = NewClient(token)
}
return nil
}
Use RequireCapabilitySetting for mandatory fields and GetCapabilitySetting for optional ones with defaults:
// Required — returns error if missing
token, err := molecule.RequireCapabilitySetting[string](config.Discovery, "token")
// Optional with default
pageSize := molecule.GetCapabilitySetting(config.Discovery, "page_size", 100)
org := molecule.GetCapabilitySetting(config.Discovery, "organization", "")
Metadata
Implement GetMetadata to declare your molecule's identity and capabilities:
func (m *MyMolecule) GetMetadata(ctx context.Context) (*molecule.MoleculeMetadata, error) {
return &molecule.MoleculeMetadata{
Namespace: "molecules.sixdegree.ai",
Name: "my-molecule",
DisplayName: "My Service Integration",
Version: "1.0.0",
Description: "Discovers resources from My Service",
Categories: []molecule.Category{molecule.CategorySourceControl},
Discovery: &molecule.DiscoveryCapability{
Supported: true,
Modes: []molecule.DiscoveryMode{molecule.DiscoveryModePolling},
ConfigSchema: discoveryConfigSchema,
EntityTypes: []molecule.EntityTypeDefinition{
{Group: "entities.sixdegree.ai", Version: "v1", Kind: "MyResource"},
},
},
MCP: &molecule.MCPCapability{
Supported: true,
ConfigSchema: mcpConfigSchema,
},
}, nil
}
MCP Tools
MCP (Model Context Protocol) tools give AI agents capabilities to interact with external services in real-time.
Declaring Tools
Declare tools in the MCP.Tools field of your metadata, then implement ListTools and CallTool:
func (m *MyMolecule) ListTools(ctx context.Context) ([]molecule.ToolDefinition, error) {
return []molecule.ToolDefinition{
{
Name: "search_resources",
Description: "Search for resources in My Service",
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["query"],
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"limit": {
"type": "integer",
"description": "Maximum results to return",
"default": 10
}
}
}`),
// Only reveal this tool after MyResource entities appear in the ontology
EntityTypes: []string{"entities.sixdegree.ai/v1/MyResource"},
},
{
Name: "create_resource",
Description: "Create a new resource",
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Resource name"
},
"description": {
"type": "string",
"description": "Resource description"
}
}
}`),
},
}, nil
}
Handling Tool Calls
Implement CallTool to dispatch tool requests:
func (m *MyMolecule) CallTool(ctx context.Context, req *molecule.ToolCallRequest) (*molecule.ToolCallResponse, error) {
switch req.Name {
case "search_resources":
return m.handleSearch(ctx, req)
case "create_resource":
return m.handleCreate(ctx, req)
default:
return &molecule.ToolCallResponse{
Success: false,
Error: fmt.Sprintf("unknown tool: %s", req.Name),
}, nil
}
}
func (m *MyMolecule) handleSearch(ctx context.Context, req *molecule.ToolCallRequest) (*molecule.ToolCallResponse, error) {
query, _ := req.Arguments["query"].(string)
limit := 10
if l, ok := req.Arguments["limit"].(float64); ok {
limit = int(l)
}
results, err := m.mcpClient.Search(ctx, query, limit)
if err != nil {
return &molecule.ToolCallResponse{
Success: false,
Error: fmt.Sprintf("search failed: %v", err),
}, nil
}
return &molecule.ToolCallResponse{
Success: true,
Result: map[string]any{
"results": results,
"count": len(results),
},
}, nil
}
func (m *MyMolecule) handleCreate(ctx context.Context, req *molecule.ToolCallRequest) (*molecule.ToolCallResponse, error) {
name, _ := req.Arguments["name"].(string)
description, _ := req.Arguments["description"].(string)
resource, err := m.mcpClient.CreateResource(ctx, name, description)
if err != nil {
return &molecule.ToolCallResponse{
Success: false,
Error: fmt.Sprintf("creation failed: %v", err),
}, nil
}
return &molecule.ToolCallResponse{
Success: true,
Result: map[string]any{
"id": resource.ID,
"name": resource.Name,
"url": resource.URL,
"message": "Resource created successfully",
},
}, nil
}
Tool Design Best Practices
1. Clear Names and Descriptions:
// Good
Name: "create_github_issue"
Description: "Create a new issue in a GitHub repository"
// Avoid
Name: "do_thing"
Description: "Does something"
2. Detailed Input Schemas:
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["repository", "title"],
"properties": {
"repository": {
"type": "string",
"description": "Repository in format owner/repo (e.g., sixdegree-ai/platform)"
},
"title": {
"type": "string",
"description": "Issue title (max 255 characters)"
},
"body": {
"type": "string",
"description": "Issue body (Markdown supported)"
},
"labels": {
"type": "array",
"description": "Labels to apply to the issue",
"items": {"type": "string"}
}
}
}`)
3. Useful Response Data:
// Return actionable information
return map[string]interface{}{
"id": issue.Number,
"title": issue.Title,
"url": issue.HTMLURL,
"state": issue.State,
"author": issue.User.Login,
}, nil
4. Error Handling:
func (m *MyMolecule) handleTool(ctx context.Context, input json.RawMessage) (interface{}, error) {
// Validate input
if err := validateInput(input); err != nil {
return nil, fmt.Errorf("invalid input: %w", err)
}
// Check permissions
if !m.hasPermission("write") {
return nil, fmt.Errorf("insufficient permissions: write access required")
}
// Perform operation with retries
var result interface{}
err := retry(ctx, 3, func() error {
var err error
result, err = m.performOperation(ctx, input)
return err
})
if err != nil {
return nil, fmt.Errorf("operation failed after retries: %w", err)
}
return result, nil
}
Webhooks (Optional)
Implement WebhookHandler alongside Discoverer for event-driven discovery:
func (m *MyMolecule) HandleWebhook(ctx context.Context, req *molecule.WebhookRequest) (*molecule.WebhookResponse, error) {
switch req.EventType {
case "resource.created", "resource.updated":
// Tell the platform to trigger incremental discovery
return &molecule.WebhookResponse{
Acknowledged: true,
TriggerDiscovery: true,
}, nil
case "resource.deleted":
var payload struct {
ResourceName string `json:"resource_name"`
}
json.Unmarshal(req.Payload, &payload)
return &molecule.WebhookResponse{
Acknowledged: true,
AffectedEntities: []molecule.EntityReference{
molecule.NewEntityRef("MyResource", payload.ResourceName, m.config.Namespace),
},
}, nil
default:
return &molecule.WebhookResponse{Acknowledged: true}, nil
}
}
Testing
Unit Testing
Test discovery logic in isolation:
package main
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDiscover(t *testing.T) {
mol := &MyMolecule{}
config := &molecule.MoleculeConfig{
Namespace: "test",
Discovery: &molecule.CapabilityConfig{
Enabled: true,
Settings: map[string]any{
"api_key": "test-key",
"base_url": "https://api.test.com",
},
},
}
err := mol.Configure(context.Background(), config)
require.NoError(t, err)
// Use a mock server via httptest (see integration testing below)
ch, err := mol.Discover(context.Background(), &molecule.DiscoveryOptions{})
require.NoError(t, err)
var entities []*molecule.Entity
for result := range ch {
if result.Error != nil {
t.Fatalf("discovery error: %v", result.Error)
}
if result.Entity != nil {
entities = append(entities, result.Entity)
}
}
assert.NotEmpty(t, entities)
assert.Equal(t, "MyResource", entities[0].Kind)
assert.NotEmpty(t, entities[0].Metadata.Name)
}
func TestCallTool(t *testing.T) {
mol := &MyMolecule{}
// List tools
tools, err := mol.ListTools(context.Background())
require.NoError(t, err)
assert.NotEmpty(t, tools)
// Call a tool
resp, err := mol.CallTool(context.Background(), &molecule.ToolCallRequest{
Name: "search_resources",
Arguments: map[string]any{"query": "test", "limit": float64(5)},
})
require.NoError(t, err)
assert.True(t, resp.Success)
}
Integration Testing with Mock Servers
Use net/http/httptest to mock the external API:
func TestWithMockServer(t *testing.T) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/resources" {
json.NewEncoder(w).Encode([]Resource{
{Name: "test-resource", URL: "https://example.com"},
})
return
}
http.NotFound(w, r)
}))
defer mockServer.Close()
mol := &MyMolecule{}
config := &molecule.MoleculeConfig{
Namespace: "test",
Discovery: &molecule.CapabilityConfig{
Enabled: true,
Settings: map[string]any{
"base_url": mockServer.URL,
"api_key": "test-key",
},
},
}
err := mol.Configure(context.Background(), config)
require.NoError(t, err)
ch, err := mol.Discover(context.Background(), &molecule.DiscoveryOptions{})
require.NoError(t, err)
var count int
for result := range ch {
if result.Entity != nil {
count++
}
}
assert.Equal(t, 1, count)
}
Testing Webhooks
func TestWebhookHandling(t *testing.T) {
mol := &MyMolecule{}
req := &molecule.WebhookRequest{
EventType: "resource.created",
Payload: json.RawMessage(`{"resource_name": "my-resource"}`),
}
resp, err := mol.HandleWebhook(context.Background(), req)
require.NoError(t, err)
assert.True(t, resp.Acknowledged)
}
Best Practices
Error Handling
1. Use Structured Errors:
import "errors"
var (
ErrUnauthorized = errors.New("unauthorized: invalid credentials")
ErrNotFound = errors.New("resource not found")
ErrRateLimit = errors.New("rate limit exceeded")
)
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
if err := m.authenticate(); err != nil {
if errors.Is(err, ErrUnauthorized) {
return nil, fmt.Errorf("authentication failed: %w", err)
}
return nil, fmt.Errorf("unexpected error: %w", err)
}
// ...
}
2. Context Cancellation:
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100)
go func() {
defer rc.Close()
// Check for cancellation before starting
select {
case <-ctx.Done():
rc.SendError("CANCELED", ctx.Err().Error())
return
default:
}
// Pass context to API calls
resources, err := m.client.ListResources(ctx)
if err != nil {
rc.SendError("FETCH_FAILED", err.Error())
return
}
// ...
}()
return rc.Chan(), nil
}
3. Retry Logic:
func retry(ctx context.Context, attempts int, fn func() error) error {
for i := 0; i < attempts; i++ {
err := fn()
if err == nil {
return nil
}
// Don't retry context errors
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return err
}
// Exponential backoff
if i < attempts-1 {
wait := time.Duration(math.Pow(2, float64(i))) * time.Second
select {
case <-time.After(wait):
case <-ctx.Done():
return ctx.Err()
}
}
}
return fmt.Errorf("failed after %d attempts", attempts)
}
Performance
1. Pagination:
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100)
go func() {
defer rc.Close()
page := 1
perPage := 100
for {
resources, hasMore, err := m.client.ListResourcesPaginated(ctx, page, perPage)
if err != nil {
rc.SendError("FETCH_FAILED", err.Error())
return
}
for _, r := range resources {
rc.SendEntity(m.convertToEntity(r))
}
if !hasMore {
break
}
page++
}
}()
return rc.Chan(), nil
}
2. Concurrent Processing:
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
orgs := molecule.GetCapabilitySetting(m.config.Discovery, "organizations", []string{})
rc := molecule.NewResultChannel(len(orgs) * 100)
go func() {
defer rc.Close()
var wg sync.WaitGroup
for _, org := range orgs {
wg.Add(1)
go func(org string) {
defer wg.Done()
entities, err := m.discoverOrganization(ctx, org)
if err != nil {
rc.SendError("ORG_FAILED", fmt.Sprintf("%s: %v", org, err))
return
}
for _, e := range entities {
rc.SendEntity(e)
}
}(org)
}
wg.Wait()
}()
return rc.Chan(), nil
}
3. Incremental Discovery:
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100)
go func() {
defer rc.Close()
// Use opts.IncrementalFrom when provided — the platform passes this for
// incremental runs (e.g., after a webhook event)
var resources []Resource
var err error
if opts.IncrementalFrom != nil {
resources, err = m.client.ListResourcesModifiedSince(ctx, *opts.IncrementalFrom)
} else {
resources, err = m.client.ListResources(ctx)
}
if err != nil {
rc.SendError("FETCH_FAILED", err.Error())
return
}
for _, r := range resources {
rc.SendEntity(m.convertToEntity(r))
}
}()
return rc.Chan(), nil
}
Logging
Use structured logging with appropriate levels:
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100)
go func() {
defer rc.Close()
rc.SendProgress(0, fmt.Sprintf("Starting discovery for namespace %s", m.config.Namespace))
// Debug: log to stderr or use slog
slog.Debug("Fetching resources", "org", org, "page", page)
if len(resources) == 0 {
slog.Warn("No resources found", "org", org)
}
if err != nil {
slog.Error("Discovery failed", "org", org, "err", err)
rc.SendError("FETCH_FAILED", err.Error())
return
}
rc.SendProgress(100, fmt.Sprintf("Discovery complete: %d entities", len(entities)))
}()
return rc.Chan(), nil
}
Packaging and Distribution
Dockerfile
Create a multi-stage Dockerfile for optimal image size:
# Build stage
FROM golang:1.24-alpine AS builder
WORKDIR /build
# Copy go modules
COPY go.mod go.sum ./
RUN go mod download
# Copy source
COPY . .
# Build with version information
ARG VERSION=dev
ARG COMMIT=unknown
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \
-o molecule \
.
# Runtime stage
FROM alpine:latest
# Add CA certificates for HTTPS
RUN apk --no-cache add ca-certificates
# Create non-root user
RUN addgroup -g 1000 molecule && \
adduser -D -u 1000 -G molecule molecule
WORKDIR /app
# Copy binary
COPY --from=builder /build/molecule /app/molecule
# Switch to non-root user
USER molecule
# Expose default port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/app/molecule", "health"]
ENTRYPOINT ["/app/molecule"]
CMD ["serve"]
Building
# Local build
docker build -t my-molecule:latest .
# Build with version
docker build \
--build-arg VERSION=1.0.0 \
--build-arg COMMIT=$(git rev-parse HEAD) \
-t my-molecule:1.0.0 \
.
# Multi-platform build
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t my-molecule:1.0.0 \
--push \
.
Publishing to Registry
1. Tag Image
docker tag my-molecule:1.0.0 registry.sixdegree.ai/my-molecule:1.0.0
docker tag my-molecule:1.0.0 registry.sixdegree.ai/my-molecule:latest
2. Push to Registry
# Login to registry
docker login registry.sixdegree.ai
# Push images
docker push registry.sixdegree.ai/my-molecule:1.0.0
docker push registry.sixdegree.ai/my-molecule:latest
3. Create Molecule Manifest
Create molecule.yaml for registry metadata:
apiVersion: sixdegree.ai/v1
kind: MoleculeManifest
metadata:
name: my-molecule
version: 1.0.0
description: Discovers resources from My Service
author: Your Name
homepage: https://github.com/yourorg/my-molecule
license: Apache-2.0
spec:
image: registry.sixdegree.ai/my-molecule:1.0.0
capabilities:
discovery:
configSchema: ./config-schema-discovery.json
mcp:
configSchema: ./config-schema-mcp.json
documentation:
readme: ./README.md
examples:
- ./examples/basic-config.yaml
- ./examples/advanced-config.yaml
testing:
examples:
- ./test/fixtures/test-config.yaml
4. Register Molecule
# Using SixDegree CLI
degree molecule publish \
--manifest molecule.yaml \
--registry registry.sixdegree.ai
# Verify publication
degree molecule search my-molecule
Versioning
Follow semantic versioning (semver):
- MAJOR (1.0.0 → 2.0.0): Breaking changes to configuration or behavior
- MINOR (1.0.0 → 1.1.0): New features, backward compatible
- PATCH (1.0.0 → 1.0.1): Bug fixes, backward compatible
// Version in code
const version = "1.2.3"
// Set via build flags
var (
version = "dev"
commit = "unknown"
buildTime = "unknown"
)
func (m *MyMolecule) GetMetadata(ctx context.Context) (*molecule.MoleculeMetadata, error) {
return &molecule.MoleculeMetadata{
Version: version,
// ...
}, nil
}
Release Process
-
Update Version:
# Update version in codesed -i 's/const version = .*/const version = "1.1.0"/' main.go# Update CHANGELOG.mdecho "## [1.1.0] - $(date +%Y-%m-%d)" >> CHANGELOG.md -
Run Tests:
go test ./... -
Build and Tag:
git tag v1.1.0git push origin v1.1.0 -
Build Image:
docker build \--build-arg VERSION=1.1.0 \--build-arg COMMIT=$(git rev-parse HEAD) \-t registry.sixdegree.ai/my-molecule:1.1.0 \. -
Publish:
docker push registry.sixdegree.ai/my-molecule:1.1.0degree molecule publish --manifest molecule.yaml
Complete Example: Project Management Molecule
A complete example showing discovery, MCP tools, and webhooks using the current SDK:
package main
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/sixdegree-ai/molecule-sdk/go/molecule"
)
const version = "1.0.0"
func main() {
molecule.Run(&ProjectMolecule{})
}
type ProjectMolecule struct {
*molecule.BaseMolecule
config *molecule.MoleculeConfig
client *ProjectClient
}
var discoveryConfigSchema = json.RawMessage(`{
"type": "object",
"required": ["api_key", "workspace_id"],
"properties": {
"api_key": {
"type": "string",
"title": "API Key",
"x-order": 1,
"x-sixdegree": {"widget": "secret"}
},
"workspace_id": {
"type": "string",
"title": "Workspace ID",
"description": "Workspace to discover projects from",
"x-order": 2
},
"include_archived": {
"type": "boolean",
"title": "Include Archived",
"default": false,
"x-order": 3
}
}
}`)
var mcpConfigSchema = json.RawMessage(`{
"type": "object",
"required": ["api_key"],
"properties": {
"api_key": {
"type": "string",
"title": "API Key",
"x-sixdegree": {"widget": "secret"}
}
}
}`)
func (m *ProjectMolecule) GetMetadata(ctx context.Context) (*molecule.MoleculeMetadata, error) {
return &molecule.MoleculeMetadata{
Namespace: "molecules.sixdegree.ai",
Name: "project-molecule",
DisplayName: "Project Management Integration",
Version: version,
Description: "Discovers projects and tasks",
Categories: []molecule.Category{molecule.CategoryProjectManagement},
Discovery: &molecule.DiscoveryCapability{
Supported: true,
Modes: []molecule.DiscoveryMode{molecule.DiscoveryModePolling, molecule.DiscoveryModeWebhook},
ConfigSchema: discoveryConfigSchema,
EntityTypes: []molecule.EntityTypeDefinition{
{Group: "entities.sixdegree.ai", Version: "v1", Kind: "PmProject"},
{Group: "entities.sixdegree.ai", Version: "v1", Kind: "PmWorkspace"},
},
},
MCP: &molecule.MCPCapability{
Supported: true,
ConfigSchema: mcpConfigSchema,
Tools: []molecule.ToolDefinition{
{
Name: "create_task",
Description: "Create a new task in a project",
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["project_id", "title"],
"properties": {
"project_id": {"type": "string", "description": "Project ID"},
"title": {"type": "string", "description": "Task title"},
"description": {"type": "string"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"],
"default": "medium"
}
}
}`),
EntityTypes: []string{"entities.sixdegree.ai/v1/PmProject"},
},
{
Name: "search_tasks",
Description: "Search for tasks across all projects",
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["query"],
"properties": {
"query": {"type": "string"},
"status": {"type": "string", "enum": ["todo", "in_progress", "done"]}
}
}`),
},
},
},
}, nil
}
func (m *ProjectMolecule) Configure(ctx context.Context, config *molecule.MoleculeConfig) error {
m.config = config
if molecule.IsCapabilityEnabled(config, molecule.CapabilityDiscovery) {
apiKey, err := molecule.RequireCapabilitySetting[string](config.Discovery, "api_key")
if err != nil {
return err
}
m.client = NewProjectClient(apiKey)
} else if molecule.IsCapabilityEnabled(config, molecule.CapabilityMCP) {
apiKey, err := molecule.RequireCapabilitySetting[string](config.MCP, "api_key")
if err != nil {
return err
}
m.client = NewProjectClient(apiKey)
}
return nil
}
func (m *ProjectMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(200)
go func() {
defer rc.Close()
workspaceID, _ := molecule.RequireCapabilitySetting[string](m.config.Discovery, "workspace_id")
includeArchived := molecule.GetCapabilitySetting(m.config.Discovery, "include_archived", false)
projects, err := m.client.ListProjects(ctx, workspaceID, includeArchived)
if err != nil {
rc.SendError("FETCH_PROJECTS_FAILED", err.Error())
return
}
rc.SendProgress(10, fmt.Sprintf("Discovered %d projects", len(projects)))
for i, proj := range projects {
projEntity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "PmProject").
Name(proj.ID).
Namespace(m.config.Namespace).
Spec("display_name", proj.Name).
Spec("description", proj.Description).
Spec("status", proj.Status).
Spec("url", proj.URL).
Build()
rc.SendEntity(projEntity)
// Only fetch tasks during full discovery or if project changed
if opts.IncrementalFrom == nil || proj.UpdatedAt.After(*opts.IncrementalFrom) {
tasks, err := m.client.ListTasks(ctx, proj.ID)
if err != nil {
rc.SendError("FETCH_TASKS_FAILED", fmt.Sprintf("project %s: %v", proj.ID, err))
continue
}
for _, task := range tasks {
taskEntity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "PmTask").
Name(task.ID).
Namespace(m.config.Namespace).
Spec("title", task.Title).
Spec("status", task.Status).
Spec("priority", task.Priority).
Spec("assignee", task.Assignee).
Build()
rc.SendEntity(taskEntity)
rel := molecule.NewRelationBuilder("BELONGS_TO").
Namespace(m.config.Namespace).
FromEntity(taskEntity).
ToEntity(projEntity).
Build()
rc.SendRelation(rel)
}
}
rc.SendProgress(10+(i+1)*90/len(projects), fmt.Sprintf("Processed %d/%d projects", i+1, len(projects)))
}
}()
return rc.Chan(), nil
}
func (m *ProjectMolecule) ListTools(ctx context.Context) ([]molecule.ToolDefinition, error) {
meta, _ := m.GetMetadata(ctx)
return meta.MCP.Tools, nil
}
func (m *ProjectMolecule) CallTool(ctx context.Context, req *molecule.ToolCallRequest) (*molecule.ToolCallResponse, error) {
switch req.Name {
case "create_task":
projectID, _ := req.Arguments["project_id"].(string)
title, _ := req.Arguments["title"].(string)
description, _ := req.Arguments["description"].(string)
priority, _ := req.Arguments["priority"].(string)
if priority == "" {
priority = "medium"
}
task, err := m.client.CreateTask(ctx, projectID, title, description, priority)
if err != nil {
return &molecule.ToolCallResponse{Success: false, Error: err.Error()}, nil
}
return &molecule.ToolCallResponse{
Success: true,
Result: map[string]any{"id": task.ID, "title": task.Title, "url": task.URL},
}, nil
case "search_tasks":
query, _ := req.Arguments["query"].(string)
status, _ := req.Arguments["status"].(string)
tasks, err := m.client.SearchTasks(ctx, query, status)
if err != nil {
return &molecule.ToolCallResponse{Success: false, Error: err.Error()}, nil
}
return &molecule.ToolCallResponse{
Success: true,
Result: map[string]any{"tasks": tasks, "count": len(tasks)},
}, nil
default:
return &molecule.ToolCallResponse{Success: false, Error: "unknown tool: " + req.Name}, nil
}
}
func (m *ProjectMolecule) HandleWebhook(ctx context.Context, req *molecule.WebhookRequest) (*molecule.WebhookResponse, error) {
switch req.EventType {
case "task.created", "task.updated", "project.updated":
return &molecule.WebhookResponse{Acknowledged: true, TriggerDiscovery: true}, nil
default:
return &molecule.WebhookResponse{Acknowledged: true}, nil
}
}
// Client stubs (simplified)
type ProjectClient struct{ apiKey string }
type Project struct{ ID, Name, Description, Status, URL string; UpdatedAt time.Time }
type Task struct{ ID, Title, Status, Priority, Assignee, URL string }
func NewProjectClient(apiKey string) *ProjectClient { return &ProjectClient{apiKey: apiKey} }
func (c *ProjectClient) ListProjects(ctx context.Context, workspaceID string, includeArchived bool) ([]Project, error) { return nil, nil }
func (c *ProjectClient) ListTasks(ctx context.Context, projectID string) ([]Task, error) { return nil, nil }
func (c *ProjectClient) CreateTask(ctx context.Context, projectID, title, description, priority string) (*Task, error) { return nil, nil }
func (c *ProjectClient) SearchTasks(ctx context.Context, query, status string) ([]Task, error) { return nil, nil }
Troubleshooting
Common Issues
Discovery returns no entities:
- Check API credentials are valid
- Verify configuration schema matches settings
- Check API rate limits
- Enable debug logging:
m.logger.Debug(...)
MCP tools not appearing:
- Verify MCP capability is enabled in configuration
- Check
ListTools()returns a non-empty slice - Validate input schema is valid JSON Schema
- If using late tool disclosure, verify the relevant entity types have been discovered first
- Check molecule server logs for errors
Webhook delivery failures:
- Verify webhook URL is accessible
- Check webhook signature validation
- Ensure HandleWebhook returns without error
- Review webhook payload format
Debugging
Enable debug logging:
# Set log level
export MOLECULE_LOG_LEVEL=debug
# Run molecule
./my-molecule serve
Test discovery without saving:
./my-molecule discover --config config.yaml --dry-run
Test MCP tools:
curl -X POST http://localhost:8080/mcp/tools/search_resources \
-H "Content-Type: application/json" \
-d '{"query": "test"}'
Deployment
Kubernetes Deployment
Deploy your molecule to Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-molecule
labels:
app: my-molecule
spec:
replicas: 1
selector:
matchLabels:
app: my-molecule
template:
metadata:
labels:
app: my-molecule
spec:
containers:
- name: molecule
image: registry.sixdegree.ai/my-molecule:1.0.0
ports:
- containerPort: 8080
env:
- name: MOLECULE_LOG_LEVEL
value: "info"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: my-molecule
spec:
selector:
app: my-molecule
ports:
- protocol: TCP
port: 8080
targetPort: 8080
Configuration Management
Store sensitive configuration in Kubernetes secrets:
apiVersion: v1
kind: Secret
metadata:
name: my-molecule-secrets
type: Opaque
stringData:
api-key: "your-api-key"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: my-molecule-config
data:
config.yaml: |
discovery:
enabled: true
settings:
base_url: "https://api.myservice.com"
workspace_id: "workspace-123"
mcp:
enabled: true
namespace: "production"
Mount secrets and config:
spec:
containers:
- name: molecule
volumeMounts:
- name: config
mountPath: /config
readOnly: true
- name: secrets
mountPath: /secrets
readOnly: true
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: my-molecule-secrets
key: api-key
volumes:
- name: config
configMap:
name: my-molecule-config
- name: secrets
secret:
secretName: my-molecule-secrets
Additional Resources
- Go SDK Documentation - Complete Go SDK reference
- Integration Guides - Real-world molecule examples
- Molecule SDK Repository - SDK source code and examples
- Molecules Repository - Official molecule implementations
Community and Support
- Discussions - Ask questions and share molecules
- Documentation - https://docs.sixdegree.ai