{
	"openapi": "3.1.0",
	"info": {
		"title": "Eigen Mesh MCP API",
		"summary": "Eigen Mesh public API: MCP (Model Context Protocol) over HTTP",
		"description": "Eigen Mesh does not expose a REST API. Its public programmatic surface is an MCP (Model Context Protocol) endpoint that speaks JSON-RPC 2.0 over a single HTTP POST path. AI assistants and other MCP clients connect with Bearer auth and call four memory tools: capture_thought, retrieve_thoughts, edit_thought, and delete_thought.\n\nProtocol: JSON-RPC 2.0. Supported methods are `tools/list` (enumerate tools) and `tools/call` (invoke a tool with `params.name` and `params.arguments`). See the MCP specification at https://modelcontextprotocol.io and the human-readable docs at https://eigenmesh.xyz/developers/mcp-tools.\n\nInternal/admin endpoints (campaigns, admin stats, etc.) are intentionally not part of this public surface and are not documented here.\n\nVersioning: this document describes v1, the current surface. Within a major version changes are additive only — new tools, new optional request fields, and new response members; clients must ignore members they do not know. Breaking changes (removing or renaming a tool, changing a required field, changing the meaning of an existing field) ship only behind a new major surface: a `/v2` base path or new tool names. The previous surface keeps working for a 6-month overlap. Deprecations are announced with the `Deprecation` and `Sunset` HTTP headers on the affected responses and in the tool description. The MCP transport follows the versioning of the Model Context Protocol specification itself (protocol revision negotiated during `initialize`).",
		"version": "1.0.0",
		"x-versioning": {
			"current": "v1",
			"status": "current",
			"policy": "Additive-only within a major version. Breaking changes ship behind a new major surface (/v2 base path or new tool names) with a 6-month overlap during which the previous surface keeps working. Deprecation is signalled with the Deprecation and Sunset HTTP headers plus a note in the tool description.",
			"additiveWithinMajor": true,
			"breakingChangeStrategy": "new major surface (/v2 or new tool names)",
			"overlapMonths": 6,
			"deprecationHeaders": ["Deprecation", "Sunset"],
			"mcp": "The MCP transport follows the versioning of the Model Context Protocol specification (protocol revision negotiated during initialize); the tool contracts above are versioned by this document."
		}
	},
	"servers": [
		{
			"url": "https://app.eigenmesh.xyz/api/mcp",
			"description": "Managed Eigen Mesh MCP endpoint"
		}
	],
	"security": [
		{
			"bearerAuth": []
		}
	],
	"tags": [
		{
			"name": "MCP",
			"description": "JSON-RPC 2.0 calls against the single MCP endpoint"
		},
		{
			"name": "Contact",
			"description": "Public website form endpoint (no authentication)"
		}
	],
	"paths": {
		"/api/mcp": {
			"post": {
				"tags": ["MCP"],
				"operationId": "mcpJsonRpc",
				"summary": "MCP JSON-RPC 2.0 endpoint",
				"description": "Single MCP endpoint. Send JSON-RPC 2.0 requests with `method` set to `tools/list` or `tools/call`. For `tools/call`, `params.name` must be one of the four public tools (see the `ToolName` enum) and `params.arguments` must match that tool's input schema. Clients should send `Accept: application/json, text/event-stream`.\n\nErrors: authentication runs before JSON-RPC parsing, so a request without a valid Bearer token is rejected with `401` and the body `{\"error\":\"Unauthorized\"}` (verified against the live endpoint). Once authenticated, JSON-RPC-level and server failures are returned as JSON-RPC 2.0 error objects (see `JsonRpcError`) — for example `-32600` invalid request, `-32601` method not found, `-32602` invalid params, `-32603` internal error, `-32000` server-side rejection.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/JsonRpcRequest"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "JSON-RPC response (result or error). Responses may be delivered as `application/json` or `text/event-stream` depending on the request Accept header.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/JsonRpcResponse"
								}
							}
						}
					},
					"400": {
						"description": "Malformed request: the body is not valid JSON, or it is not a well-formed JSON-RPC 2.0 request. Body is a JSON-RPC 2.0 error object (see `JsonRpcError`), typically code `-32700` (parse error) or `-32600` (invalid request).",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/JsonRpcError"
								}
							}
						}
					},
					"401": {
						"description": "Missing or invalid Bearer token. Authentication runs before JSON-RPC parsing, so an unauthenticated request returns `{\"error\":\"Unauthorized\"}` (verified against the live endpoint). Authorization failures detected after parsing are returned as a JSON-RPC 2.0 error object (see `JsonRpcError`).",
						"content": {
							"application/json": {
								"schema": {
									"oneOf": [
										{
											"$ref": "#/components/schemas/UnauthorizedError"
										},
										{
											"$ref": "#/components/schemas/JsonRpcError"
										}
									]
								}
							}
						}
					},
					"406": {
						"description": "Request is not acceptable: the Accept header must include `application/json` (and/or `text/event-stream`)."
					},
					"429": {
						"description": "Too many requests. Body is a JSON-RPC 2.0 error object (see `JsonRpcError`). Back off and retry; no fixed quota is published.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/JsonRpcError"
								}
							}
						}
					},
					"500": {
						"description": "Unexpected server error while handling the request. Body is a JSON-RPC 2.0 error object (see `JsonRpcError`), typically code `-32603` (internal error).",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/JsonRpcError"
								}
							}
						}
					}
				}
			}
		},
		"/contact": {
			"servers": [
				{
					"url": "https://eigenmesh.xyz/api",
					"description": "Eigen Mesh website API — `/contact` resolves to https://eigenmesh.xyz/api/contact"
				}
			],
			"post": {
				"tags": ["Contact"],
				"operationId": "contactSubmit",
				"summary": "Submit the Eigen Enterprise contact form",
				"description": "Public endpoint behind the contact form on https://eigenmesh.xyz/contact — no authentication required. Required fields: `name`, `email`, `message`; `organization` is optional. Validation order: body is not valid JSON → `400 invalid_json`; a required field is missing → `400 missing_fields`; `email` fails the basic address shape check → `400 invalid_email`. A `200` means the message was accepted and handed to the email sender; it is not a delivery receipt.\n\nRate limiting: 5 requests per 10 minutes per client IP (fixed window, first `x-forwarded-for` IP). The counter is in-memory, so it is per instance and resets on deploy. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`; a `429` also carries `Retry-After` (seconds).",
				"security": [],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/ContactRequest"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Message accepted and handed to the email sender.",
						"headers": {
							"RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ContactSuccess"
								}
							}
						}
					},
					"400": {
						"description": "Invalid request body. `error.code` is one of `invalid_json`, `missing_fields`, `invalid_email`.",
						"headers": {
							"RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorEnvelope"
								}
							}
						}
					},
					"405": {
						"description": "Method not allowed — only `POST` submits the form. `error.code` is `method_not_allowed`.",
						"headers": {
							"RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorEnvelope"
								}
							}
						}
					},
					"429": {
						"description": "Rate limit exceeded (5 requests / 10 minutes per client IP). `error.code` is `rate_limited`.",
						"headers": {
							"RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							},
							"Retry-After": {
								"description": "Seconds to wait before retrying.",
								"schema": {
									"type": "integer"
								}
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorEnvelope"
								}
							}
						}
					},
					"500": {
						"description": "The message could not be handed to the email sender. `error.code` is `send_failed`.",
						"headers": {
							"RateLimit-Limit": {
								"$ref": "#/components/headers/RateLimitLimit"
							},
							"RateLimit-Remaining": {
								"$ref": "#/components/headers/RateLimitRemaining"
							},
							"RateLimit-Reset": {
								"$ref": "#/components/headers/RateLimitReset"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorEnvelope"
								}
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"headers": {
			"RateLimitLimit": {
				"description": "Requests allowed per window (5).",
				"schema": {
					"type": "integer"
				}
			},
			"RateLimitRemaining": {
				"description": "Requests left in the current window.",
				"schema": {
					"type": "integer"
				}
			},
			"RateLimitReset": {
				"description": "Seconds until the current window resets.",
				"schema": {
					"type": "integer"
				}
			}
		},
		"securitySchemes": {
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer",
				"description": "MCP API key issued in the Eigen Mesh app. Default auth attributes captures to the agent (the API key's name); pass `as_user: true` to capture_thought to store the human user's memory."
			}
		},
		"schemas": {
			"ErrorEnvelope": {
				"type": "object",
				"description": "Uniform error body used by the website HTTP endpoints (for example /api/contact). MCP/JSON-RPC responses use JsonRpcError instead.",
				"required": ["error"],
				"properties": {
					"error": {
						"type": "object",
						"required": ["code", "message"],
						"properties": {
							"code": {
								"type": "string",
								"description": "Stable machine-readable code, e.g. invalid_json, missing_fields, invalid_email, rate_limited, send_failed, method_not_allowed."
							},
							"message": {
								"type": "string",
								"description": "Human-readable explanation."
							}
						}
					}
				}
			},
			"JsonRpcError": {
				"type": "object",
				"description": "JSON-RPC 2.0 error response, as returned by the MCP endpoint for JSON-RPC-level and server failures.",
				"required": ["jsonrpc", "id", "error"],
				"properties": {
					"jsonrpc": {
						"type": "string",
						"const": "2.0"
					},
					"id": {
						"description": "Echo of the request id, or null when the id could not be determined.",
						"type": ["integer", "string", "null"]
					},
					"error": {
						"type": "object",
						"required": ["code", "message"],
						"properties": {
							"code": {
								"type": "integer",
								"description": "JSON-RPC error code: -32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error, -32000 server-side rejection."
							},
							"message": {
								"type": "string"
							},
							"data": {
								"description": "Optional structured detail about the failure."
							}
						}
					}
				}
			},
			"UnauthorizedError": {
				"type": "object",
				"description": "Body returned by the MCP endpoint when the Bearer token is missing or invalid (authentication runs before JSON-RPC parsing).",
				"required": ["error"],
				"properties": {
					"error": {
						"type": "string",
						"const": "Unauthorized"
					}
				}
			},
			"ContactRequest": {
				"type": "object",
				"description": "Eigen Enterprise early-access request submitted by the website contact form.",
				"required": ["name", "email", "message"],
				"properties": {
					"name": {
						"type": "string",
						"description": "Sender name. Required, non-empty after trimming."
					},
					"email": {
						"type": "string",
						"description": "Reply-to address. Required; checked against a basic address shape (`local@domain.tld`)."
					},
					"message": {
						"type": "string",
						"description": "Message body. Required, non-empty after trimming."
					},
					"organization": {
						"type": "string",
						"description": "Optional organisation, included in the notification email when present."
					}
				}
			},
			"ContactSuccess": {
				"type": "object",
				"description": "Returned when the message was accepted and handed to the email sender.",
				"required": ["ok"],
				"properties": {
					"ok": {
						"type": "boolean",
						"const": true
					}
				}
			},
			"JsonRpcRequest": {
				"type": "object",
				"required": ["jsonrpc", "id", "method"],
				"properties": {
					"jsonrpc": {
						"type": "string",
						"const": "2.0"
					},
					"id": {
						"description": "Request identifier, echoed back in the response.",
						"type": ["integer", "string"]
					},
					"method": {
						"type": "string",
						"enum": ["tools/list", "tools/call"]
					},
					"params": {
						"type": "object",
						"description": "For tools/list: empty object. For tools/call: the tool name and its arguments.",
						"properties": {
							"name": {
								"$ref": "#/components/schemas/ToolName"
							},
							"arguments": {
								"description": "Arguments for the tool selected via `params.name`. Must match that tool's input schema (see CaptureThoughtArguments, RetrieveThoughtsArguments, EditThoughtArguments, DeleteThoughtArguments).",
								"type": "object"
							}
						}
					}
				}
			},
			"JsonRpcResponse": {
				"type": "object",
				"required": ["jsonrpc", "id"],
				"properties": {
					"jsonrpc": {
						"type": "string",
						"const": "2.0"
					},
					"id": {
						"type": ["integer", "string"]
					},
					"result": {
						"type": "object",
						"description": "For tools/call, result.content[0].text carries the tool output.",
						"properties": {
							"content": {
								"type": "array",
								"items": {
									"$ref": "#/components/schemas/ContentBlock"
								}
							}
						}
					},
					"error": {
						"type": "object",
						"properties": {
							"code": {
								"type": "integer",
								"description": "JSON-RPC error code, e.g. -32000 for auth/acceptance issues."
							},
							"message": {
								"type": "string"
							}
						}
					}
				}
			},
			"ContentBlock": {
				"type": "object",
				"properties": {
					"type": {
						"type": "string",
						"enum": ["text"]
					},
					"text": {
						"type": "string"
					}
				}
			},
			"ToolName": {
				"type": "string",
				"enum": ["capture_thought", "retrieve_thoughts", "edit_thought", "delete_thought"],
				"description": "The four tools exposed over HTTP MCP. Notes tools (create_text_file, search_text_files, etc.) are available in the in-app chat agent only."
			},
			"CaptureThoughtArguments": {
				"type": "object",
				"description": "Capture and store a raw thought. Tier 1: returns immediately after text persist; keyword recall on lexical_text is ready. Tier 2 (background): embedding, entities, graph links. Use retrieve_thoughts for recall. MCP Bearer auth: default labels the thought as agent-authored (your API key name). Pass as_user true when the user asked you to remember something for them (their memory, not yours). Omit as_user when storing the agent's own observation or note.",
				"properties": {
					"raw": {
						"type": "string"
					},
					"captured_at": {
						"type": "string",
						"description": "Optional ISO-8601 capture time for backdated memories (temporal anchoring)."
					},
					"as_user": {
						"type": "boolean",
						"description": "When true, store as the human user's memory (e.g. they asked you to remember this for them). When false or omitted on MCP API key auth, store as agent-authored with your API key name."
					},
					"author": {
						"type": "string",
						"description": "Optional override: first ~10 characters of a different API key prefix to attribute authorship. Usually omitted — MCP Bearer token identity is used automatically."
					}
				},
				"required": ["raw"]
			},
			"RetrieveThoughtsArguments": {
				"type": "object",
				"description": "Read stored thoughts. Defaults to user-authored open memories (excludes agent captures and completed/archived). For the latest open thoughts (newest first): omit query or set order=created_at — use top_k and optional cursor_created_at + cursor_id to paginate. With query and order=relevance (default): hybrid semantic, lexical, and graph search over open thoughts plus lexical search over attached text notes. Pass author=all or include_agent=true to include agent/API-key captures.",
				"properties": {
					"query": {
						"type": "string",
						"description": "Optional search text. Omit (or set order=created_at) to browse recent open thoughts newest-first instead of searching."
					},
					"order": {
						"type": "string",
						"enum": ["created_at", "relevance"],
						"description": "created_at: newest open thoughts first (ignores query). relevance: rank by search match (default; requires query)."
					},
					"top_k": {
						"type": "number",
						"description": "Max results (default 10). When browsing recent thoughts, acts as the page size."
					},
					"threshold": {
						"type": "number"
					},
					"mode": {
						"type": "string",
						"enum": ["fast", "full"]
					},
					"detail": {
						"type": "string",
						"enum": ["snippet", "full"]
					},
					"author": {
						"type": "string",
						"enum": ["user", "agent", "all"],
						"description": "Whose memories to retrieve. Default user (human captures). agent = API-key captures only. all = no author filter."
					},
					"include_agent": {
						"type": "boolean",
						"description": "When true, same as author=all (ignored if author is set)."
					},
					"cursor_created_at": {
						"type": "string",
						"description": "Pagination cursor (ISO created_at) when browsing recent thoughts without query."
					},
					"cursor_id": {
						"type": "string",
						"description": "Pagination cursor (thought UUID) when browsing recent thoughts without query."
					}
				}
			},
			"EditThoughtArguments": {
				"type": "object",
				"description": "Edit an existing thought by ID with a natural-language request. Covers text changes (reword, fix typo) and lifecycle/status changes (mark complete, mark done, reopen, archive, dismiss as irrelevant/outdated). Works for ANY category — task, idea, observation, fact, etc. are interchangeable; never refuse because something is \"not a todo\". Done/complete sets completed; archive/irrelevant/outdated soft-removes like delete_thought. There is no separate set_status tool.",
				"properties": {
					"thought_id": {
						"type": "string"
					},
					"edit_request": {
						"type": "string",
						"description": "Natural-language instruction, e.g. \"mark as done\", \"mark complete\", \"archive\", \"not relevant\", \"outdated\", \"fix typo in second sentence\". Category does not matter."
					},
					"raw_text": {
						"type": "string",
						"description": "Optional direct text replacement. When provided, replaces the thought text directly without LLM processing."
					}
				},
				"required": ["thought_id"]
			},
			"DeleteThoughtArguments": {
				"type": "object",
				"description": "Archive (soft-remove) one stored thought by ID — reversible, not a permanent delete. Same soft-remove family as edit_thought \"archive\" / \"not relevant\" / \"outdated\"; use for delete/remove. Works for any category.",
				"properties": {
					"thought_id": {
						"type": "string"
					}
				},
				"required": ["thought_id"]
			}
		}
	}
}
