The MCP client article described connecting KaryoSpace outward to other tools' servers. This article describes the other direction: KaryoSpace as an MCP server, exposing all your org data so that Claude Desktop, Claude Code, or any MCP-compatible AI client can query your emails, incidents, Jira tickets, and knowledge base directly.
The pitch is simple. A developer opens Claude Desktop, adds one config entry, and their AI assistant instantly knows what is happening across the org. No migration. No new tool. Just a server that speaks MCP.
KaryoSpace ships a standalone binary, workspace-mcp, compiled from backend/go/cmd/mcp/main.go. It exposes five tools over MCP:
search_workspace(query, scope?) // Email + org RAG search
list_incidents(status?, priority?) // Open + resolved tickets
get_knowledge(query) // Knowledge base docs
list_tasks(project?, assignee?) // PM tasks
get_email_thread(thread_id) // Full email thread
Each tool maps to an internal Go function call, not an HTTP call. The MCP binary imports the same packages as the main app and calls the store functions directly. This means zero network hop between the MCP layer and the database.
MCP supports two transports. Stdio is for local clients (Claude Desktop, Claude Code): the client spawns the binary as a subprocess and communicates over stdin/stdout with newline-delimited JSON-RPC 2.0. HTTP+SSE is for remote clients: the server listens on a port, the client connects via HTTP, and the server pushes responses as Server-Sent Events.
KaryoSpace implements both. The binary detects which transport to use based on whether it is connected to a terminal:
func main() {
if !isatty.IsTerminal(os.Stdin.Fd()) {
// Stdin is a pipe — stdio transport (Claude Desktop)
runStdio()
} else {
// Interactive — start HTTP+SSE server
port := os.Getenv("MCP_PORT")
if port == "" { port = "8081" }
runHTTPSSE(port)
}
}
The MCP server has no session cookies. Authentication is an API key passed in the --token flag. On startup, the key is looked up in the mcp_api_keys MongoDB collection, which stores the org ID and user ID associated with each key.
type MCPKey struct {
KeyHash string // SHA-256 of the raw key
OrgID string
UserID string
Name string // "Claude Desktop - Suman"
LastUsed time.Time
Created time.Time
}
func resolveKey(raw string) (*MCPKey, error) {
h := sha256.Sum256([]byte(raw))
hash := hex.EncodeToString(h[:])
return db.Collection("mcp_api_keys").FindOne(
ctx, bson.M{"key_hash": hash},
).Decode(&MCPKey{})
}
All tool calls run under the resolved user context. search_workspace with scope "email" only returns emails owned by that user. Incident queries are org-scoped. The same authorization checks that protect the web app protect the MCP interface.
MCP requires each tool to declare a JSON Schema for its input parameters. The client sends this schema to the AI model so it knows what arguments to provide. Here is the schema for search_workspace:
MCPTool{
Name: "search_workspace",
Description: "Search emails, incidents, knowledge docs, and chat " +
"messages across the org using natural language.",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"query": map[string]any{
"type": "string",
"description": "Natural language search query",
},
"scope": map[string]any{
"type": "string",
"enum": []string{"email", "org", "all"},
"description": "email=user-scoped; org=all org data; all=both",
"default": "all",
},
},
"required": []string{"query"},
},
}
When the MCP client sends a tools/call request, the server dispatches to the registered handler. Here is the list_incidents handler:
func handleListIncidents(ctx context.Context, key *MCPKey, args map[string]any) ([]MCPContent, error) {
filter := bson.M{"org_id": key.OrgID}
if s, ok := args["status"].(string); ok && s != "" {
filter["status"] = s
}
if p, ok := args["priority"].(string); ok && p != "" {
filter["priority"] = p
}
limit := int64(20)
incidents, err := incidentStore.List(ctx, filter, &limit)
if err != nil { return nil, err }
var lines []string
for _, inc := range incidents {
lines = append(lines, fmt.Sprintf("[%s] %s — %s (%s)",
inc.ID, inc.Title, inc.Status, inc.Priority))
}
return []MCPContent{{Type: "text", Text: strings.Join(lines, "
")}}, nil
}
This is the complete config a user adds to their Claude Desktop settings. It is everything required to point Claude at a running KaryoSpace instance:
{
"mcpServers": {
"karyospace": {
"command": "workspace-mcp",
"args": ["--org", "yourcompany.com", "--token", "mcp_YOUR_API_KEY"],
"env": {}
}
}
}
After adding this, Claude Desktop automatically discovers the five tools and surfaces them when relevant. A user can type "what are the open P1 incidents this week?" and Claude will call list_incidents(status="open", priority="critical") and return a formatted response from your live data.
The MCP server turns KaryoSpace from a workspace application into AI infrastructure. An org does not need to replace their existing tools or migrate data. They install KaryoSpace, connect it to their existing Gmail and Jira, and point Claude Desktop at the MCP server. Within minutes, their AI assistant has cross-org context across email, tickets, docs, and chat.
This is Mode 2 in the product vision: the AI context layer that sits on top of existing tools. The MCP server is the technical mechanism that makes it possible. It is also the reason the product does not need to win a migration argument to get customers. The conversation changes from "replace your tools" to "add intelligence to the tools you already have."
Questions about KaryoSpace or this article? We'd love to hear from you.