{
  "openapi": "3.1.0",
  "info": {
    "title": "LeanScale GTM Knowledge API",
    "version": "1.0.0",
    "summary": "Programmatic access to LeanScale's go-to-market operations knowledge base.",
    "description": "Two surfaces.\n\n**MCP** (`/mcp`) is the primary one: a Model Context Protocol server over Streamable HTTP, exposing the knowledge base as tools and resources to Claude, Cursor and any other MCP client. Discovery (`initialize`, `tools/list`, `resources/list`, `resources/read`) is unauthenticated; `tools/call` needs a key.\n\n**REST** (`/api/access`) exists to issue that key.\n\nKeys are free. `POST /api/access` with a business email returns one immediately.\n\nThe server is also listed in the official MCP registry as `team.leanscale/gtm-knowledge`.",
    "contact": {
      "name": "LeanScale",
      "url": "https://www.leanscale.team/mcp",
      "email": "hello@leanscale.team"
    }
  },
  "servers": [
    {
      "url": "https://mcp.leanscale.team",
      "description": "Production. The MCP endpoint and the key-issuing API both live on this host."
    }
  ],
  "externalDocs": {
    "description": "Human documentation for the MCP server",
    "url": "https://www.leanscale.team/mcp"
  },
  "tags": [
    {
      "name": "mcp",
      "description": "Model Context Protocol endpoint (JSON-RPC 2.0 over Streamable HTTP)."
    },
    {
      "name": "access",
      "description": "Key issuance."
    }
  ],
  "paths": {
    "/mcp": {
      "get": {
        "operationId": "getMcpServerMetadata",
        "tags": [
          "mcp"
        ],
        "summary": "Describe the MCP server",
        "description": "Unauthenticated health and discovery. Returns the server identity, the transport, the supported protocol versions, the tool names and live corpus counts. Use this to confirm the endpoint is reachable before attempting an MCP session.",
        "security": [],
        "responses": {
          "200": {
            "description": "Server metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpServerMetadata"
                }
              }
            }
          },
          "default": {
            "description": "Any other status, including 405 when the HTTP method is not one of the three above. Uses the same error envelope as the rest of this API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "callMcpJsonRpc",
        "tags": [
          "mcp"
        ],
        "summary": "Call an MCP method",
        "description": "JSON-RPC 2.0. Supported methods: `initialize`, `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, `prompts/list`.\n\n`tools/call` requires a bearer key and returns JSON-RPC error `-32001` without one. Every other method is open so a client can inspect the server before signing up.\n\nFailures of the JSON-RPC call itself are carried in the `error` member with HTTP 200, per JSON-RPC 2.0 \u2014 a non-200 here means the request never reached the protocol layer.",
        "parameters": [
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": false,
            "description": "Negotiated protocol revision. Omit to accept the server default.",
            "schema": {
              "type": "string",
              "enum": [
                "2025-06-18",
                "2025-03-26",
                "2024-11-05",
                "2025-11-25"
              ]
            }
          },
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "Access key, as a fallback for MCP clients that cannot set request headers. Prefer the Authorization header.",
            "schema": {
              "type": "string",
              "pattern": "^ls_pk_[A-Za-z0-9]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response. Protocol-level failures are carried in `error` with HTTP 200, per JSON-RPC.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted. No body."
          },
          "default": {
            "description": "Any other status, including 405 when the HTTP method is not one of the three above. Uses the same error envelope as the rest of this API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "endMcpSession",
        "tags": [
          "mcp"
        ],
        "summary": "End an MCP session",
        "description": "Streamable HTTP session teardown. This server is stateless, so the call always succeeds and does nothing.",
        "security": [],
        "responses": {
          "204": {
            "description": "Session ended."
          },
          "default": {
            "description": "Any other status, including 405 when the HTTP method is not one of the three above. Uses the same error envelope as the rest of this API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "summary": "MCP endpoint",
      "description": "GET describes the server, POST carries JSON-RPC, DELETE ends a session. Any other HTTP method returns 405 with the standard error envelope. That 405 is documented on each operation's `default` response rather than as an explicit 4XX, because it belongs to methods this path does not define \u2014 declaring it on GET, POST or DELETE would document a response none of them can return."
    },
    "/api/access": {
      "post": {
        "operationId": "issueAccessKey",
        "tags": [
          "access"
        ],
        "summary": "Issue an access key",
        "description": "Exchanges a business email for a free access key. Consumer mailboxes are rejected and the domain is checked for deliverability. Repeat signups return the key already issued rather than a new one, so an installed key is never orphaned.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccessRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key issued, or returned unchanged for a repeat signup.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed. `code` identifies which check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Wrong method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "accessKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "An access key from `POST /api/access`. Required only by `tools/call`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every failing response on this API has this shape.",
        "required": [
          "ok",
          "error",
          "code"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Human-readable message, safe to show a user."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable code. Branch on this, never on the message.",
            "enum": [
              "method_not_allowed",
              "invalid_json",
              "name_required",
              "company_required",
              "invalid_business_email",
              "email_domain_not_found",
              "email_domain_no_mx",
              "not_found"
            ]
          },
          "field": {
            "type": "string",
            "description": "Request field the error belongs to, when it belongs to one."
          },
          "hint": {
            "type": "string",
            "description": "What to change to make the call succeed."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "Link to this specification."
          }
        }
      },
      "AccessRequest": {
        "type": "object",
        "required": [
          "name",
          "company",
          "email"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120,
            "description": "Who is asking."
          },
          "company": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160,
            "description": "Company name."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Business email. Consumer domains are rejected."
          },
          "role": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Job title. Optional."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 600,
            "description": "What they want to use it for. Optional."
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "Honeypot. Leave absent \u2014 it is not rendered for humans and any value marks the submission as automated."
          }
        }
      },
      "AccessResponse": {
        "type": "object",
        "required": [
          "ok",
          "key"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "key": {
            "type": "string",
            "pattern": "^ls_pk_[A-Za-z0-9]+$",
            "description": "The access key. Send as a bearer token to /mcp."
          },
          "tier": {
            "type": "string",
            "enum": [
              "public",
              "internal"
            ],
            "description": "What the key can reach."
          },
          "returning": {
            "type": "boolean",
            "description": "Present and true when this email already had a key."
          },
          "stats": {
            "$ref": "#/components/schemas/CorpusStats"
          }
        }
      },
      "CorpusStats": {
        "type": "object",
        "description": "Live corpus counts. Distinct parents on the public tier.",
        "properties": {
          "documents": {
            "type": "integer",
            "description": "Searchable passages."
          },
          "characters": {
            "type": "integer"
          },
          "playbooks": {
            "type": "integer"
          },
          "studies": {
            "type": "integer"
          },
          "caseStudies": {
            "type": "integer"
          },
          "episodes": {
            "type": "integer"
          },
          "docs": {
            "type": "integer"
          }
        }
      },
      "McpServerMetadata": {
        "type": "object",
        "required": [
          "ok",
          "server",
          "transport",
          "endpoint"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "server": {
            "type": "object",
            "description": "MCP serverInfo.",
            "properties": {
              "name": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "version": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "websiteUrl": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "transport": {
            "type": "string",
            "const": "streamable-http"
          },
          "endpoint": {
            "type": "string",
            "format": "uri"
          },
          "auth": {
            "type": "string",
            "description": "How to present a key."
          },
          "protocolVersions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tool names available."
          },
          "corpus": {
            "$ref": "#/components/schemas/CorpusStats"
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ],
            "description": "Omit for notifications."
          },
          "method": {
            "type": "string",
            "description": "MCP method name.",
            "enum": [
              "initialize",
              "ping",
              "tools/list",
              "tools/call",
              "resources/list",
              "resources/read",
              "prompts/list",
              "notifications/initialized",
              "notifications/cancelled"
            ]
          },
          "params": {
            "type": "object",
            "description": "Method parameters. Shape depends on `method`."
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "result": {
            "type": "object",
            "description": "Present on success."
          },
          "error": {
            "$ref": "#/components/schemas/JsonRpcError"
          }
        }
      },
      "JsonRpcError": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "description": "-32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params, -32603 internal error, -32002 resource not found, -32001 access key required.",
            "enum": [
              -32700,
              -32600,
              -32601,
              -32602,
              -32603,
              -32002,
              -32001
            ]
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "description": "Extra context, such as the list of valid resource URIs."
          }
        }
      }
    }
  },
  "security": [
    {
      "accessKey": []
    }
  ]
}
