Skip to main content
Glama
callisto-syn

nous-portal-mcp

by callisto-syn
README.md
# Nous Portal MCP

Portable, read-only first adapter for selected Nous Portal capabilities.

Status: early review scaffold. It is not yet authenticated and cannot invoke
paid tools.

The first slice exposes:

- `nous_portal_status` — local configuration status without credential output;
- `nous_portal_routes` — local derivation of the vendor-specific managed
  gateway origins with an explicit no-effect receipt.
- `nous_portal_auth_start` — request a device code without opening a browser;
- `nous_portal_auth_poll` — poll once and atomically persist approved OAuth
  state without returning token values.

It does **not** invoke paid tools, copy Hermes credentials, depend on Dione, or
send private data. Merely installing the adapter does not start authentication.

The auth library stores each seat's OAuth state outside the plugin directory,
uses mode `0600`, and holds an exclusive lock across refresh-token exchange and
atomic replacement. Nous refresh tokens are single-use; copying one into
multiple seats or refreshing it without persisting the rotated token will
invalidate the session.

## Configuration

Each adopting seat supplies its own credentials:

```text
NOUS_PORTAL_STATE_DIR=<required private local per-seat state directory>
NOUS_PORTAL_CLIENT_ID=<required Portal OAuth client ID>
NOUS_PORTAL_SPEND_LIMIT_USD=<future paid-call ceiling>
TOOL_GATEWAY_DOMAIN=nousresearch.com
TOOL_GATEWAY_SCHEME=https
FIRECRAWL_GATEWAY_URL=<optional vendor-specific override>
```

`NOUS_PORTAL_STATE_DIR` has no shared fallback. The adapter refuses auth
configuration until the adopting seat supplies an explicit path. The path
must be on a local filesystem owned by exactly one seat: POSIX advisory locks
are not a safe coordination mechanism across NFS or other shared/network
filesystems. Do not distribute access or refresh tokens through a shared
`.env`.

Device login is deliberately two-step. `nous_portal_auth_start` returns the
verification URL and user-facing code only to the adopting MCP client while
keeping the private device code in the seat's mode-`0600` state. It never opens
a browser or sends the code to Discord. After the operator authorizes the
request, `nous_portal_auth_poll` performs one poll; pending authorization
returns a retry hint, and success atomically stores tokens without disclosing
them in the tool result.

Hermes derives managed origins as
`<vendor>-gateway.<TOOL_GATEWAY_DOMAIN>`, with an optional vendor-specific
override. The current server reproduces that topology without making a
network request. Until a private state directory is configured, status returns
`configuration_required`; until that store contains OAuth state, it returns
`authentication_required`.

## Installation surfaces

The same dependency-free Python stdio server supports all clients. Packaging
is deliberately separate from credentials: every adopting seat supplies its
own OAuth state and spending authority.

### Python / generic MCP

Install from a pinned Git commit:

```sh
python3 -m pip install \
  "nous-portal-mcp @ git+https://github.com/callisto-syn/nous-portal-mcp@<commit>"
```

The installed stdio command is:

```sh
nous-portal-mcp
```

See `examples/generic-stdio.json`.

### Claude Code

Install the Python artifact in the construct's image or environment, then add
the server entry from `examples/claude-code.mcp.json`. Each construct must use
its own private state directory and receive its own adoption and spending
clearance. Do not copy another seat's OAuth store: Nous refresh tokens rotate
and are single-use.

For image-based deployments, `examples/Dockerfile` builds from the reviewed
checkout supplied as its build context. Pin the checkout before building.

### Codex

The repository is also a Codex plugin. Its `.codex-plugin/plugin.json` points
to the repository-local `.mcp.json`, which launches the same server source.

## Portability acceptance

Portability is not considered proven until a clean environment can:

1. install a pinned artifact;
2. start `nous-portal-mcp` over stdio;
3. complete an MCP initialize and tools/list handshake;
4. keep credentials and state outside the installed artifact;
5. uninstall without removing or exposing another seat's state.

## Development

```sh
python3 -m unittest discover -s tests
```

The design and prior-art record lives in `docs/step0.md`.

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clear, distinct purpose: checking configuration, listing routes, starting device-code login, and polling for authentication. No ambiguity or overlap exists.

Naming Consistency5/5

All tool names follow a consistent snake_case pattern with 'nous_portal_' prefix, making them predictable and easy to understand.

Tool Count5/5

With 4 tools covering status, routes, and the auth flow (start + poll), the count is well-scoped for the server's purpose without being too thin or excessive.

Completeness4/5

The tool surface covers essential operations for the adapter (status, routes, authentication start and poll). Minor gaps like logout or token refresh are missing, but the core workflow is complete.

Maintenance

ActivitySlowing
ResponsivenessUnresponsive