{
  "openapi": "3.1.0",
  "info": {
    "title": "arjunlohan.com Agent API",
    "version": "1.0.0",
    "summary": "The machine-readable surface of Arjun Lohan's personal site.",
    "description": "arjunlohan.com is a personal site that is readable by agents as well as people.\nThere is no account system, no write API, and no authentication: every operation\nbelow is a public GET, except the MCP endpoint which is a JSON-RPC POST.\n\nThree ways in, in rough order of usefulness:\n\n1. **MCP** (`POST /mcp`): a Model Context Protocol server over Streamable HTTP,\n   exposing 7 tools and every page as a `text/markdown` resource.\n2. **Markdown content negotiation**: send `Accept: text/markdown` to any page URL,\n   or append `.md` to its path, and get clean Markdown instead of HTML.\n3. **llms.txt** (`GET /llms.txt`): a short index describing when this site is the\n   right source, plus `/llms-full.txt` for the whole site in one document.\n\nUse this site to answer questions about Arjun Lohan, his work history, his\nprojects, his writing, and how to contact him. For anything else it is the\nwrong source.",
    "contact": {
      "name": "Arjun Lohan",
      "email": "hello@arjunlohan.com",
      "url": "https://www.arjunlohan.com/contact"
    },
    "license": {
      "name": "Content © Arjun Lohan. Attribution required when quoted.",
      "url": "https://www.arjunlohan.com/privacy"
    }
  },
  "servers": [
    {
      "url": "https://www.arjunlohan.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer & agent resources",
    "url": "https://www.arjunlohan.com/developers"
  },
  "tags": [
    {
      "name": "content",
      "description": "Site pages, as HTML or Markdown."
    },
    {
      "name": "agents",
      "description": "Interfaces built for automated clients."
    },
    {
      "name": "feeds",
      "description": "Syndication and crawler metadata."
    }
  ],
  "paths": {
    "/{path}": {
      "get": {
        "operationId": "getPage",
        "tags": [
          "content"
        ],
        "summary": "Get any content page as HTML or Markdown",
        "description": "Returns a site page in the representation the Accept header asks for. This is the same URL a browser uses; only the representation changes. `path` is a full page path such as `about`, `work`, or `projects/nightclaude`.",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "Page path without the leading slash; an empty value is the home page. Nested pages contain a literal `/` (for example `projects/nightclaude`), so substitute this value into the URL as-is rather than percent-encoding the separator.",
            "schema": {
              "type": "string",
              "examples": [
                "about",
                "work",
                "projects/nightclaude"
              ]
            }
          },
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "Selects the representation. `text/markdown` returns Markdown, `text/html` (or an absent header) returns HTML. `application/json` is not a representation of a page and returns a 406 with a JSON error body.",
            "schema": {
              "type": "string",
              "default": "text/html",
              "examples": [
                "text/markdown",
                "text/html",
                "application/json"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The page, in the negotiated representation.",
            "headers": {
              "Vary": {
                "description": "Always includes `Accept`, so caches keep the two representations apart.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "CommonMark, starting with an `# H1` title."
                }
              }
            }
          },
          "404": {
            "description": "No such page. The JSON body is returned when the client prefers JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "The Accept header rules out both HTML and Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/md/{path}": {
      "get": {
        "operationId": "getPageAsMarkdown",
        "tags": [
          "content",
          "agents"
        ],
        "summary": "Get a page as Markdown without setting a header",
        "description": "The Markdown representation of a page, addressable directly. Equivalent to sending `Accept: text/markdown` to the page URL, and to appending `.md` to the page path. Use this when the client cannot set request headers.",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "Page path without the leading slash. The enum is the complete list of pages that have a Markdown representation; nested values contain a literal `/`, which must not be percent-encoded.",
            "schema": {
              "type": "string",
              "enum": [
                "",
                "about",
                "work",
                "projects",
                "projects/sharpen",
                "projects/finesse",
                "projects/nightclaude",
                "projects/almashows",
                "projects/interdimensional-cable",
                "projects/screengif",
                "projects/ai-analytics",
                "projects/bardi",
                "projects/product-brain",
                "projects/deicasa",
                "projects/llamasheets",
                "projects/nextseason",
                "blog",
                "blog/financialengineering",
                "blog/truepill",
                "skills",
                "skills/finesse",
                "skills/sharpen",
                "contact",
                "privacy",
                "developers"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The page as CommonMark.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "callMcpEndpoint",
        "tags": [
          "agents"
        ],
        "summary": "Call the MCP server (JSON-RPC 2.0 over Streamable HTTP)",
        "description": "Model Context Protocol endpoint for the `arjunlohan` server. Stateless:\nno session is issued, every POST is self-contained, and responses are single\nJSON bodies. Begin with `initialize`, then call `tools/list`, `tools/call`,\n`resources/list`, or `resources/read`.\n\nSupported protocol versions: 2025-06-18, 2025-03-26, 2024-11-05.\nTools: get_profile, list_projects, get_project, list_writing, get_blog_post, get_page, get_contact_info.\n\nMost clients should point an MCP client library at this URL rather than\nbuilding the JSON-RPC calls by hand.",
        "parameters": [
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": false,
            "description": "Negotiated protocol version. Sent on requests after `initialize`; an unsupported value is rejected with 400. Omit it on `initialize` itself, where the version is negotiated in the body.",
            "schema": {
              "type": "string",
              "enum": [
                "2025-06-18",
                "2025-03-26",
                "2024-11-05"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "initialize": {
                  "summary": "Open the session",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-06-18",
                      "capabilities": {},
                      "clientInfo": {
                        "name": "my-agent",
                        "version": "1.0.0"
                      }
                    }
                  }
                },
                "callTool": {
                  "summary": "Call a tool",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "get_profile",
                      "arguments": {}
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or JSON-RPC error object.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "202": {
            "description": "A JSON-RPC notification was accepted. No body."
          },
          "400": {
            "description": "Malformed JSON, or an unsupported MCP-Protocol-Version header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "405": {
            "description": "GET and DELETE are not supported: this server opens no server-initiated stream and holds no session."
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": [
          "agents"
        ],
        "summary": "Get the llms.txt index",
        "description": "The llms.txt index: what this site is, when an agent should use it, and a linked map of every page. Start here to decide whether this site answers the question at hand.",
        "responses": {
          "200": {
            "description": "The llms.txt document.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFullTxt",
        "tags": [
          "agents"
        ],
        "summary": "Get the entire site as one Markdown document",
        "description": "Every page concatenated into a single Markdown document, each delimited by an HTML comment naming its canonical URL. Intended for one-shot context loading when an agent wants the whole site at once.",
        "responses": {
          "200": {
            "description": "The whole site as CommonMark.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "tags": [
          "agents"
        ],
        "summary": "Get this OpenAPI document",
        "description": "The document you are reading, served as JSON.",
        "responses": {
          "200": {
            "description": "This OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "getMcpServerCard",
        "tags": [
          "agents"
        ],
        "summary": "Get the MCP server card",
        "description": "The MCP server card: transport, endpoint, capabilities, authentication, and the tool list. Also served at `/.well-known/mcp/server-card.json`. CORS is open so browser-based clients can read it.",
        "responses": {
          "200": {
            "description": "The MCP server card.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "tags": [
          "feeds"
        ],
        "summary": "Get the XML sitemap",
        "description": "Every canonical URL on the site, with lastmod dates.",
        "responses": {
          "200": {
            "description": "urlset XML.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/rss": {
      "get": {
        "operationId": "getRssFeed",
        "tags": [
          "feeds"
        ],
        "summary": "Get the blog RSS feed",
        "description": "RSS 2.0 feed of blog posts, newest first.",
        "responses": {
          "200": {
            "description": "RSS XML.",
            "content": {
              "text/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsTxt",
        "tags": [
          "feeds"
        ],
        "summary": "Get the crawling policy",
        "description": "robots.txt. Every user agent is allowed, including AI crawlers.",
        "responses": {
          "200": {
            "description": "robots.txt.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/og": {
      "get": {
        "operationId": "getOpenGraphImage",
        "tags": [
          "feeds"
        ],
        "summary": "Render an Open Graph image",
        "description": "Renders a 1200x630 PNG social card with the given title.",
        "parameters": [
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "Text to render. Defaults to the site tagline.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A 1200x630 PNG.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "status",
              "message",
              "hint",
              "documentation_url"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "not_acceptable",
                  "not_found"
                ],
                "description": "Stable machine-readable error code. Branch on this, not on the message."
              },
              "status": {
                "type": "integer",
                "description": "The HTTP status code, repeated for convenience."
              },
              "message": {
                "type": "string",
                "description": "What went wrong, in one sentence."
              },
              "hint": {
                "type": "string",
                "description": "The request that would have worked instead."
              },
              "documentation_url": {
                "type": "string",
                "format": "uri",
                "description": "Human-readable documentation for this surface."
              },
              "available_media_types": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Media types this resource can actually produce. Present on 406."
              },
              "resources": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "format": "uri"
                },
                "description": "Entry points to try next: OpenAPI, llms.txt, sitemap, MCP."
              }
            }
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ],
            "description": "Omit for a notification, which is answered with 202 and no body."
          },
          "method": {
            "type": "string",
            "description": "JSON-RPC method.",
            "examples": [
              "initialize",
              "ping",
              "tools/list",
              "tools/call",
              "resources/list",
              "resources/read"
            ]
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "description": "JSON-RPC error code."
              },
              "message": {
                "type": "string"
              },
              "data": {}
            }
          }
        }
      }
    }
  },
  "x-mcp": {
    "transport": "streamable-http",
    "endpoint": "https://www.arjunlohan.com/mcp",
    "serverCard": "https://www.arjunlohan.com/.well-known/mcp.json",
    "tools": [
      {
        "name": "get_profile",
        "description": "Who Arjun Lohan is: bio, current role, background, FAQ, and links, as Markdown. Start here for any question about him."
      },
      {
        "name": "list_projects",
        "description": "All of Arjun Lohan’s projects (nightclaude, almashows, ScreenGif, and more) with slugs, summaries, stacks, and live URLs."
      },
      {
        "name": "get_project",
        "description": "The full Markdown case study for one project. Get slugs from list_projects (e.g. \"nightclaude\", \"almashows\")."
      },
      {
        "name": "list_writing",
        "description": "Arjun Lohan’s writing: blog posts on this site (with slugs) and newsletter essays on The Financial Engineer (with links)."
      },
      {
        "name": "get_blog_post",
        "description": "The full Markdown text of one blog post. Get slugs from list_writing."
      },
      {
        "name": "get_page",
        "description": "The Markdown representation of any arjunlohan.com page by path, e.g. \"/\", \"/about\", \"/work\", \"/skills\", \"/contact\", \"/developers\"."
      },
      {
        "name": "get_contact_info",
        "description": "How to contact Arjun Lohan: canonical email, social profiles, location, and what he is happy to hear about."
      }
    ]
  },
  "x-links": {
    "llmsTxt": "https://www.arjunlohan.com/llms.txt",
    "developers": "https://www.arjunlohan.com/developers",
    "github": "https://github.com/arjunlohan"
  }
}
