# Independent L1 MCP Worker

This module serves MCP independently of SFCRM AppService and connector services.
It consumes published Runtime CRM APIs using the signed-in user's identity.
Runtime owns current object, field and record permissions for reads and writes.
There is no MCP Connected App, local token verifier, permission database or
MCP-specific scope/profile. Architecture and remaining acceptance:
[design authority](../../docs/design/mcp-l1-worker.md).

## Implemented surface

- `POST /mcp`: initialize, ping, tools/list, tools/call and notifications.
- `crm_query_records` / `crm_query_my_records`: explicit selected fields, scalar
  comparisons, AND filters, ordering and bounded pagination. Runtime supplies
  the current principal for “my records”.
- `crm_mutate_record`: one CREATE, UPDATE or DELETE through Runtime's
  single-record mutation use case; use `crm_mutation_status` with the exact same
  input after an ambiguous result. Keep the idempotency key and payload stable.
- `GET /livez`, `/readyz`, `/version`, `/capabilities`: published process host.
- `/.well-known/oauth-protected-resource`: configured resource and header bearer
  method only. No authorization-server route or automatic OAuth onboarding is
  advertised. This is a **preconfigured native-session bearer** integration;
  it is not acceptance for OAuth-only hosted clients or legacy MCP OAuth/PATs.

Example tool arguments (no user/workload/permission selectors):

```json
{"object":"Account","fields":["Id","Name"],"filters":[{"field":"Name","operator":"EQUAL","value":"Example"}],"limit":20}
```

```json
{"operation":"CREATE","object":"Account","fields":{"Name":"Example"},"idempotency_key":"unique-intended-write-key"}
```

Results contain authorized fields only; omission stays distinct from null and
numbers are not rounded through float64. Unknown arguments, legacy formula
strings and unsupported filters fail explicitly. The `crm_*` names identify a
new tool contract; old `list_objects`/`describe_object` and raw filters are not
silently aliased to incomplete implementations.

## Build and run

```sh
./check.sh
go build -o /tmp/mcp-worker ./cmd/mcp-worker
```

`go.mod` pins published L0 client/contract modules; no Core checkout or local
replacement is needed. The executable requires:

| Environment | Meaning |
| --- | --- |
| `MCP_PUBLIC_ORIGIN` | Canonical externally served HTTPS origin (HTTP only on loopback) |
| `MCP_RUNTIME_TARGET` | Private Runtime gRPC endpoint |
| `MCP_APP_INSTALLATION_ID` | Existing CRM installation selected by deployment authority |
| `MCP_WORKLOAD_TOKEN_FILE` | Private regular file (0400/0600), no symlink, containing this Worker's separately registered single-line workload credential |
| `MCP_HTTP_ADDRESS` | Listener, default `127.0.0.1:8092`; configure private proxy ingress explicitly |
| `MCP_RUNTIME_CA_FILE`, `MCP_RUNTIME_SERVER_NAME` | Runtime TLS trust options |
| `MCP_RUNTIME_CLIENT_CERT_FILE`, `MCP_RUNTIME_CLIENT_KEY_FILE` | Optional Runtime mTLS client identity |

Do not reuse the AppService's workload token. Register this Worker's separate
identity in Runtime transport credentials and serving v4 `authorized_workload_ids`
and `user_api_callers` (sorted unique lists). The caller must not also be a
Catalog handler owner or workload/Catalog consumer. The prepared installation
must provide query/mutation APIs at the current Runtime security epoch. This
uses the [independent user API caller contract](https://github.com/pingcap-inc/NexusCRM/pull/4837);
it does not change handler ownership or require Catalog republication. These
are process admission and API compatibility declarations, not MCP business permissions. The Runtime's
existing session capabilities still apply to the presented credential. A Worker
cannot remove restrictions from a credential already restricted by its issuer.
Native federated sessions are the audited no-Connected-App source path.

For an HTTP loopback public origin, the listener must also be a literal
loopback address (`127.0.0.1` or `::1`); wildcard, external and hostname binds
are rejected before reading credentials or connecting to Runtime.
For HTTPS public origins, the HTTP listener belongs behind trusted TLS ingress; only exact configured
Origin is allowed when supplied. Query/protocol admission selects `runtime.records.query.v1`; mutation and mutation-status
admission select `runtime.records.mutate.v1`. A query delegation is never used
as write authority. Authorization uses the shared published Runtime
client's short-lived delegation coordination. Runtime re-admits every business
RPC against current user/session and permission state. Worker holds no separate
user permission cache. All requests have a 30-second budget and Runtime attempts
a 10-second budget. SIGTERM drains active requests before closing clients.

Build a candidate image from a dedicated context containing the Linux binary
named `mcp-worker` plus this Dockerfile. Match `GOARCH=amd64` to
`docker build --platform linux/amd64` (also on an arm64 development host).
Do not use the repository root's
AppService-only Docker context. The `mcp-worker` workflow runs credential-free
protocol checks on PRs; trusted main downloads private pins, runs the full
check/build and validates the image. Successful main builds publish an independent `nexuscrm-mcp-worker` image with
linked BuildKit SBOM, signed SLSA provenance and a Sigstore keyless signature.
The owner policy binds the GitHub main workflow and exact source SHA. After signature verification, an attempt-scoped `mcp-release` artifact records
the immutable image digest and source commit; no mutable source tag is a release
authority. L2 verifies the
signature, provenance and SBOM, seals the digest into its signed BOM, and owns
environment deployment; this build does not deploy an environment.

Current tests include protocol boundaries, HTTP-to-injected-Runtime user
propagation/denial, exact numeric values, filter preservation, idempotency,
error masking and drain. These are not live Runtime permission-equivalence or
Claude/Codex login acceptance. Host-only live acceptance and its exact candidate identities are recorded in
[Core #4837](https://github.com/pingcap-inc/NexusCRM/pull/4837). This does not prove
Claude/Codex login or formal release acceptance. Object metadata discovery,
client login and signed Worker release delivery remain separate rollout work.

The current single-record Runtime executor does not implement UPSERT despite
that arm being defined in the semantic contract. This Worker neither advertises
nor accepts it; it must not emulate upsert through a query-then-write race.

Mutation and status outcomes use an explicit MCP response (`operation`, `success`,
optional record/created facts, and errors with `code`, `message`, optional `fields`).
Runtime carriers are never serialized directly. Internal/unknown error text and
fields, source-guard objects/actions and command internals are omitted. Only the
published CUSTOM_VALIDATION/POLICY_DENIED display messages are retained, matching
the existing CRM user-facing error boundary.

### Signing identity and retry authority

The certificate SHA selector intentionally checks the **build source commit**.
Fulcio maps `github-workflow-sha` to the GitHub OIDC `sha` claim, and cosign's
`--certificate-github-workflow-sha` flag checks that extension. It does not check
a workflow-file blob or the last commit that edited the YAML. The workflow path
and main ref are independently bound by `--certificate-identity`. See the
[pinned Fulcio mapping](https://github.com/sigstore/fulcio/blob/12080b1e8be8561ca3be3e3a6beb8c194b14e7fc/config/identity/config.yaml#L223-L224)
and [pinned cosign flag definition](https://github.com/sigstore/cosign/blob/633e8f4303b7db64c369bdfa315374a8bd511ac6/cmd/cosign/cli/options/certificate.go#L74-L75).
Worker-only main commits therefore remain valid without editing workflow code.

The published version is an immutable image digest, not a mutable source-SHA
registry tag. The build job retains its exact digest in the immutable
`mcp-candidate` Actions artifact for that same run before signing can start.
A partial failure reuses that digest (within the 30-day artifact retention),
verifies existing attestations/signatures and creates only missing attachments.
The verified release descriptor is uploaded as `mcp-release-<attempt>` with
90-day retention. L2 selects its exact digest and source SHA and re-verifies
signature/provenance/SBOM before signing the deployment BOM. Diagnostic build
tags are never consulted as release authority and cannot redirect a sealed BOM.
