> For the complete documentation index, see [llms.txt](https://docs.ohalee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ohalee.com/products/obsidian-license/mcp-server.md).

# AI Agent (MCP)

Connect an AI coding assistant to your Obsidian License account over the Model Context Protocol, at https\://license.ohalee.com/mcp.

Our [Model Context Protocol](https://modelcontextprotocol.io) endpoint lets an AI coding assistant (Claude Code, or any MCP client) work with your licensing directly. It gives the agent two things:

1. **A way in.** Manage products, issue and revoke license keys, promote and roll back versions, and read activation logs, as the account behind your API key.
2. **The knowledge to use it.** Ten implementation guides plus a project scaffolder covering both modes end to end: the one-call license check for an ordinary jar, and the full protected-module route with the `ProtectedModule` contract, the `Obsidian-Module` manifest attribute, the classloader rules, tiers and entitlements, the loader, publishing, and the wire protocol.

The knowledge half needs no credentials, so the agent can learn the whole mechanism before it touches your account.

## Setup

Nothing to install. We serve MCP at **`https://license.ohalee.com/mcp`**, and there are two ways to authenticate.

### Sign in from your client (recommended)

Add the endpoint with no credentials. Your client opens a browser, you sign in, and it keeps itself authenticated from there.

```bash
claude mcp add --transport http obsidian-license https://license.ohalee.com/mcp
```

We implement the MCP authorization spec end to end: protected-resource and authorization-server discovery, dynamic client registration, and an authorization-code flow with PKCE. Your client handles the whole dance, so all you do is approve the request in the browser. Access tokens last 12 hours and are refreshed automatically, so you are not pasting anything again tomorrow.

{% hint style="info" %}
If your client reports **"Dynamic Client Registration rejected"** or **"Cannot POST /register"**, it is talking to a build from before OAuth support landed. Update the server (self-hosters) or reconnect, and make sure the URL is exactly `https://license.ohalee.com/mcp` with no trailing path.
{% endhint %}

### Or use an API key

Better for CI, headless machines, and anywhere a browser is not available. Create one on the API Keys page and send it as a bearer token:

```bash
claude mcp add --transport http obsidian-license https://license.ohalee.com/mcp \
  --header "Authorization: Bearer obl_your_key"
```

Or in a client that reads a JSON config:

```json
{
  "mcpServers": {
    "obsidian-license": {
      "type": "http",
      "url": "https://license.ohalee.com/mcp",
      "headers": { "Authorization": "Bearer obl_your_key" }
    }
  }
}
```

However you authenticate, the endpoint acts as your account, with the same permissions, plan limits and audit trail as the dashboard. It is stateless: each request gets its own server scoped to the token it carried, so nothing is kept between calls and nothing is shared between accounts. Send JSON-RPC with `POST`; a `GET` returns 405, because there is no session to stream into.

{% hint style="warning" %}
An API key carries your account's permissions. Treat it like a password, keep it out of your repository, and revoke it from the API Keys page (or with `obsidian_revoke_api_key`) if it leaks.
{% endhint %}

## The local option

The platform also ships a **stdio** MCP server in the `mcp-server/` package, which runs on your own machine. It has the same license tools and the same guides, plus the three things the hosted endpoint cannot do, since that one runs on our servers and cannot see your disk:

* **Upload a built jar** from a local path, with a check that the jar really carries its `Obsidian-Module` manifest attribute.
* **Write a generated starter project** straight into a folder.
* **Sign in with your email and password** instead of carrying an API key. The agent calls `obsidian_login` and asks you for a TOTP code when two-factor is on; the password is used once and never stored, and the 12 hour token is cached with `0600` permissions.

```bash
cd mcp-server
npm install && npm run build
claude mcp add obsidian-license \
  --env OBSIDIAN_URL=https://license.ohalee.com \
  --env OBSIDIAN_API_KEY=obl_your_key \
  -- node /absolute/path/to/mcp-server/dist/index.js
```

| Variable                 | Meaning                                                                  |
| ------------------------ | ------------------------------------------------------------------------ |
| `OBSIDIAN_URL`           | Your license server, `https://license.ohalee.com` for the hosted service |
| `OBSIDIAN_API_KEY`       | Personal API key (`obl_...`), optional                                   |
| `OBSIDIAN_MCP_NO_CACHE`  | `1` keeps a login token in memory only                                   |
| `OBSIDIAN_MCP_STATE_DIR` | Where that token is cached (default `~/.obsidian-license/`)              |

There is deliberately no default server URL: leaving it unset is safer than guessing one and sending your password somewhere you did not choose.

## What the agent can do

**Session:** `obsidian_login`, `obsidian_logout`, `obsidian_whoami`, `obsidian_set_server`, `obsidian_create_api_key`, `obsidian_list_api_keys`, `obsidian_revoke_api_key`

**Products:** `obsidian_list_products`, `obsidian_create_product`, `obsidian_update_product`, `obsidian_upload_payload`, `obsidian_list_versions`, `obsidian_promote_version`, `obsidian_set_product_trial`, `obsidian_delete_product`, `obsidian_server_key`

**Licenses:** `obsidian_list_licenses`, `obsidian_create_license`, `obsidian_bulk_create_licenses`, `obsidian_update_license`, `obsidian_revoke_license`, `obsidian_reactivate_license`, `obsidian_reset_license_activations`, `obsidian_delete_license`

**Diagnostics:** `obsidian_activation_logs`, `obsidian_stats`

**Knowledge:** `obsidian_guide`, `obsidian_scaffold_java`

The hosted endpoint carries all of these except the ones that need your filesystem or an interactive login: `obsidian_login`, `obsidian_logout`, `obsidian_set_server` and `obsidian_upload_payload` are stdio only, and `obsidian_scaffold_java` returns the files as text rather than writing them.

`obsidian_upload_payload` checks the jar's `Obsidian-Module` manifest attribute before uploading anything: a jar without it uploads happily and then fails on every customer's server.

## The guides

`obsidian_guide` returns the topic below as markdown. They are also exposed as resources at `obsidian://guide/<slug>`, and there are two prompts, `protect-java-plugin` and `diagnose-activation`.

| Topic              | Covers                                                                   |
| ------------------ | ------------------------------------------------------------------------ |
| `overview`         | The moving parts and what happens on a customer's server                 |
| `license-check`    | Licensing an ordinary jar with one call: the client, seats, entitlements |
| `java-integration` | Gradle setup, the entry class, the manifest attribute, classloader rules |
| `migrating-plugin` | Translating an existing `JavaPlugin`, and the state that breaks reloads  |
| `entitlements`     | Selling several SKUs from one build with the signed entitlement block    |
| `loader-config`    | Building the loader and what customers install                           |
| `publishing`       | Products, uploads, version channels, CI                                  |
| `licensing`        | Seats, revocation, marketplaces, trials, spotting abuse                  |
| `protocol`         | The wire format, the signed envelope, every denial code                  |
| `troubleshooting`  | What each failure actually means                                         |

## Things to ask it

* "Convert this plugin to a protected module and publish it as `my-plugin`."
* "A customer says the plugin will not start. Their key is OBS-... Work out why."
* "Issue 50 keys for `my-plugin` with 2 seats each and give me the CSV."
* "Put this build on the beta channel and move my three testers onto it."
* "Add a premium tier to `my-plugin` with a `max_homes` entitlement, and show me the code that reads it."

{% hint style="info" %}
The agent is acting on your live account. Ask it to confirm before it revokes, deletes, or promotes anything, and check what it proposes on destructive calls.
{% endhint %}
