Install
$ agentstack add mcp-freepeak-cortex ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Cortex
Build MCP Servers Declaratively in Golang
Table of Contents
- [Overview](#overview)
- [Installation](#installation)
- [Quickstart](#quickstart)
- [What is MCP?](#what-is-mcp)
- [Core Concepts](#core-concepts)
- [Server](#server)
- [Tools](#tools)
- [Providers](#providers)
- [Resources](#resources)
- [Prompts](#prompts)
- [Running Your Server](#running-your-server)
- [STDIO](#stdio)
- [HTTP with SSE](#http-with-sse)
- [Multi-Protocol](#multi-protocol)
- [Testing and Debugging](#testing-and-debugging)
- [Embedding Cortex](#embedding-cortex)
- [HTTP Server Integration](#http-server-integration)
- [PocketBase Integration](#pocketbase-integration)
- [Examples](#examples)
- [Basic Examples](#basic-examples)
- [Advanced Examples](#advanced-examples)
- [Plugin System](#plugin-system)
- [Package Structure](#package-structure)
- [Contributing](#contributing)
- [License](#license)
- [Support & Contact](#support--contact)
Overview
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. Cortex implements the full MCP specification, making it easy to:
- Build MCP servers that expose resources and tools
- Use standard transports like stdio and Server-Sent Events (SSE)
- Handle all MCP protocol messages and lifecycle events
- Follow Go best practices and clean architecture principles
- Embed Cortex into existing servers and applications
> Note: Cortex is always updated to align with the latest MCP specification from spec.modelcontextprotocol.io/latest
Installation
go get github.com/FreePeak/cortex
Quickstart
Let's create a simple MCP server that exposes an echo tool:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/FreePeak/cortex/pkg/server"
"github.com/FreePeak/cortex/pkg/tools"
)
func main() {
// Create a logger that writes to stderr instead of stdout
// This is critical for STDIO servers as stdout must only contain JSON-RPC messages
logger := log.New(os.Stderr, "[cortex] ", log.LstdFlags)
// Create the server
mcpServer := server.NewMCPServer("Echo Server Example", "1.0.0", logger)
// Create an echo tool
echoTool := tools.NewTool("echo",
tools.WithDescription("Echoes back the input message"),
tools.WithString("message",
tools.Description("The message to echo back"),
tools.Required(),
),
)
// Example of a tool with array parameter
arrayExampleTool := tools.NewTool("array_example",
tools.WithDescription("Example tool with array parameter"),
tools.WithArray("values",
tools.Description("Array of string values"),
tools.Required(),
tools.Items(map[string]interface{}{
"type": "string",
}),
),
)
// Add the tools to the server with handlers
ctx := context.Background()
err := mcpServer.AddTool(ctx, echoTool, handleEcho)
if err != nil {
logger.Fatalf("Error adding tool: %v", err)
}
err = mcpServer.AddTool(ctx, arrayExampleTool, handleArrayExample)
if err != nil {
logger.Fatalf("Error adding array example tool: %v", err)
}
// Write server status to stderr instead of stdout to maintain clean JSON protocol
fmt.Fprintf(os.Stderr, "Starting Echo Server...\n")
fmt.Fprintf(os.Stderr, "Send JSON-RPC messages via stdin to interact with the server.\n")
fmt.Fprintf(os.Stderr, `Try: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","parameters":{"message":"Hello, World!"}}}\n`)
// Serve over stdio
if err := mcpServer.ServeStdio(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
// Echo tool handler
func handleEcho(ctx context.Context, request server.ToolCallRequest) (interface{}, error) {
// Extract the message parameter
message, ok := request.Parameters["message"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid 'message' parameter")
}
// Return the echo response in the format expected by the MCP protocol
return map[string]interface{}{
"content": []map[string]interface{}{
{
"type": "text",
"text": message,
},
},
}, nil
}
// Array example tool handler
func handleArrayExample(ctx context.Context, request server.ToolCallRequest) (interface{}, error) {
// Extract the values parameter
values, ok := request.Parameters["values"].([]interface{})
if !ok {
return nil, fmt.Errorf("missing or invalid 'values' parameter")
}
// Convert values to string array
stringValues := make([]string, len(values))
for i, v := range values {
stringValues[i] = v.(string)
}
// Return the array response in the format expected by the MCP protocol
return map[string]interface{}{
"content": stringValues,
}, nil
}
What is MCP?
The Model Context Protocol (MCP) is a standardized protocol that allows applications to provide context for LLMs in a secure and efficient manner. It separates the concerns of providing context and tools from the actual LLM interaction. MCP servers can:
- Expose data through Resources (read-only data endpoints)
- Provide functionality through Tools (executable functions)
- Define interaction patterns through Prompts (reusable templates)
- Support various transport methods (stdio, HTTP/SSE)
Core Concepts
Server
The MCP Server is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:
// Create a new MCP server with logger
mcpServer := server.NewMCPServer("My App", "1.0.0", logger)
Tools
Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
// Define a calculator tool
calculatorTool := tools.NewTool("calculator",
tools.WithDescription("Performs basic math operations"),
tools.WithString("operation",
tools.Description("The operation to perform (add, subtract, multiply, divide)"),
tools.Required(),
),
tools.WithNumber("a",
tools.Description("First operand"),
tools.Required(),
),
tools.WithNumber("b",
tools.Description("Second operand"),
tools.Required(),
),
)
// Add the tool to the server with a handler
mcpServer.AddTool(ctx, calculatorTool, handleCalculator)
Providers
Providers allow you to group related tools and resources into a single package that can be easily registered with a server:
// Create a weather provider
weatherProvider, err := weather.NewWeatherProvider(logger)
if err != nil {
logger.Fatalf("Failed to create weather provider: %v", err)
}
// Register the provider with the server
err = mcpServer.RegisterProvider(ctx, weatherProvider)
if err != nil {
logger.Fatalf("Failed to register weather provider: %v", err)
}
Resources
Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
// Create a resource (Currently using the internal API)
resource := &domain.Resource{
URI: "sample://hello-world",
Name: "Hello World Resource",
Description: "A sample resource for demonstration purposes",
MIMEType: "text/plain",
}
Prompts
Prompts are reusable templates that help LLMs interact with your server effectively:
// Create a prompt (Currently using the internal API)
codeReviewPrompt := &domain.Prompt{
Name: "review-code",
Description: "A prompt for code review",
Template: "Please review this code:\n\n{{.code}}",
Parameters: []domain.PromptParameter{
{
Name: "code",
Description: "The code to review",
Type: "string",
Required: true,
},
},
}
// Note: Prompt support is being updated in the public API
Running Your Server
MCP servers in Go can be connected to different transports depending on your use case:
STDIO
For command-line tools and direct integrations:
// Start a stdio server
if err := mcpServer.ServeStdio(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
IMPORTANT: When using STDIO, all logs must be directed to stderr to maintain the clean JSON-RPC protocol on stdout:
// Create a logger that writes to stderr
logger := log.New(os.Stderr, "[cortex] ", log.LstdFlags)
// All debug/status messages should use stderr
fmt.Fprintf(os.Stderr, "Server starting...\n")
HTTP with SSE
For web applications, you can use Server-Sent Events (SSE) for real-time communication:
// Configure the HTTP address
mcpServer.SetAddress(":8080")
// Start an HTTP server with SSE support
if err := mcpServer.ServeHTTP(); err != nil {
log.Fatalf("HTTP server error: %v", err)
}
// For graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := mcpServer.Shutdown(ctx); err != nil {
log.Fatalf("Server shutdown error: %v", err)
}
Multi-Protocol
You can also run multiple protocol servers simultaneously by using goroutines:
// Start an HTTP server
go func() {
if err := mcpServer.ServeHTTP(); err != nil {
log.Fatalf("HTTP server error: %v", err)
}
}()
// Start a STDIO server
go func() {
if err := mcpServer.ServeStdio(); err != nil {
log.Fatalf("STDIO server error: %v", err)
}
}()
// Wait for shutdown signal
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
## Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [FreePeak](https://github.com/FreePeak)
- **Source:** [FreePeak/cortex](https://github.com/FreePeak/cortex)
- **License:** Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.