feat: compose authenticated stdio MCP with explicit runtime tool mapping
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 3s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
tegwick 2026-09-28 12:38:04 +02:00
parent 1ece969f59
commit 3c5cbfbafe
14 changed files with 465 additions and 81 deletions

View file

@ -120,10 +120,13 @@ MCP hosts may supply `token_provider`, a callable resolving a **Hub-audience**
credential from the current invocation, with `require_credentials=True`. Credentials
are never retained on the server; requests do not follow redirects when carrying
one. `trailing_slash=False` targets the standalone runtime's native paths. The
standalone production MCP has no human flow/provider yet and fails closed.
standalone production MCP supports only explicit single-principal stdio credentials;
network transports require an authenticated host composition. The standalone
profile advertises four mapped runtime tools; legacy operations remain embedded-only.
Many legacy tools target APIs the standalone Hub does not implement. They remain
unsupported, not silently translated or authorized. Cross-audience delegation,
browser PKCE sessions/logout and real MCP root login remain open.
unsupported, not silently translated or authorized. Cross-audience delegation
remains open. Browser PKCE sessions/logout are implemented locally; live browser/MCP caller
admission remains open. See the [MCP composition contract](owner-access-integration.md#mcp-caller-composition-and-backend-mapping).
## Remaining release gates

View file

@ -51,7 +51,8 @@ harness reports only the implemented profile above.
`make ci-check` runs the test suite, checks the reviewed access inventory for
source drift, builds distributions and validates an installed wheel outside the
checkout's import path. Forgejo runs these gates for `main` pushes and manual
checkout's import path. The wheel check uses a fresh environment and refreshes
the Hub package so rebuilding the same version cannot reuse an older installation. Forgejo runs these gates for `main` pushes and manual
runs, using the full commit SHA and a unique temporary checkout. CI installs the
locked development and runtime dependencies first. Individual gates are
`make test`, `make inventory-check` and `make package-check`.

View file

@ -152,3 +152,56 @@ query strings are cleared before response-time application access logging, but
reverse proxies and tracing collectors must independently suppress callback
queries, cookies and authorization headers. No public listener is enabled by
these source changes.
## MCP caller composition and backend mapping
The standalone CLI now registers only four tools with equivalent runtime APIs:
| MCP tool | Runtime GET route |
| --- | --- |
| `query_repository_navigation` | `/ports/projections/repository-navigation/repositories` |
| `get_repository_navigation_facet` | `/ports/projections/repository-navigation/facets/{facet_kind}/{facet_value}` |
| `query_workloads` | `/ports/projections/workloads` |
| `resolve_workload_reference` | `/ports/projections/workloads/resolve` |
These use exact paths without redirect-based slash normalization. The other 27
generic tools require embedded host APIs: state/domain orientation, message
read/write/reply, capabilities/requests, repo/DOI operations, service/TPSC and
legacy progress operations. They remain available in the SDK's default
`backend_profile="embedded"`; they are not advertised by the standalone runtime
profile. Native message/event commands are not equivalent replacements for legacy
thread, recipient, author or event semantics. The inventory records each tool's
backend profiles. Host-specific routing and caller admission remain T04/T05 work.
For a **private, single-principal stdio process**, an operator can project an
already admitted Hub-audience credential and run:
```sh
HUB_CORE_ENV=production hub-core mcp --transport stdio \
--api-base https://hub.example --token-file /run/secrets/hub-mcp-caller
```
The file is reread for every request, with bounded size and sanitized errors.
Missing, invalid or expired credentials cannot fall back to anonymous/shared-root
access. Expiry, issuer, audience and current grants are checked by the Hub API.
The CLI neither obtains nor refreshes credentials; the projecting owner controls
rotation. This example is a composition interface, not an issued grant or live
acceptance. Do not share this process between principals or use an operator/root
token as an automated workload identity.
Enforced CLI network transports refuse startup: a shared MCP endpoint needs a
host that authenticates callers. `--token-file` is rejected for every network
transport, including development. The host SDK composition supplies
`token_provider=current_invocation_hub_credential`, `require_credentials=True`,
and an explicit backend profile. The provider must resolve the authenticated
invocation's **Hub-audience** credential; forwarding an MCP-audience token or
putting a static root token in the callback is not an admitted composition.
The SDK does not implement token exchange, delegation or host authentication.
Authenticated outbound requests require non-local HTTPS, never follow redirects,
and ignore proxy environment variables. Tools never accept credentials as model
arguments. Facet path segments reject traversal/separator/query injection.
Tests exercise actual FastMCP tool invocation with distinct concurrent caller
contexts, file rotation/removal, route-catalog admission for all four runtime
tools, enforced backend denial and CLI transport restrictions. They do not claim
that an external MCP consumer or workload has been admitted.

View file

@ -1203,14 +1203,17 @@
"tool": "accept_capability_request",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 232,
"line": 248,
"target_calls": [
{
"method": "POST",
"path_expression": "f'/capability-requests/{request_id}/accept/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:append_progress",
@ -1218,14 +1221,17 @@
"tool": "append_progress",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 487,
"line": 503,
"target_calls": [
{
"method": "POST",
"path_expression": "'/progress/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:check_repo_doi",
@ -1233,14 +1239,17 @@
"tool": "check_repo_doi",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 393,
"line": 409,
"target_calls": [
{
"method": "GET",
"path_expression": "f'/repos/{repo_slug}/doi/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_alerts",
@ -1248,14 +1257,17 @@
"tool": "get_alerts",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 474,
"line": 490,
"target_calls": [
{
"method": "GET",
"path_expression": "'/progress/alerts/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_capability_request",
@ -1263,14 +1275,17 @@
"tool": "get_capability_request",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 274,
"line": 290,
"target_calls": [
{
"method": "GET",
"path_expression": "f'/capability-requests/{request_id}/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_doi_summary",
@ -1278,14 +1293,17 @@
"tool": "get_doi_summary",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 402,
"line": 418,
"target_calls": [
{
"method": "GET",
"path_expression": "'/repos/doi/summary/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_domain",
@ -1293,14 +1311,17 @@
"tool": "get_domain",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 112,
"line": 128,
"target_calls": [
{
"method": "GET",
"path_expression": "f'/domains/{domain_slug}/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_domain_summary",
@ -1308,14 +1329,17 @@
"tool": "get_domain_summary",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 108,
"line": 124,
"target_calls": [
{
"method": "GET",
"path_expression": "f'/domains/{domain_slug}/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_gdpr_report",
@ -1323,14 +1347,17 @@
"tool": "get_gdpr_report",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 457,
"line": 473,
"target_calls": [
{
"method": "GET",
"path_expression": "'/tpsc/report/gdpr/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_messages",
@ -1338,14 +1365,17 @@
"tool": "get_messages",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 137,
"line": 153,
"target_calls": [
{
"method": "GET",
"path_expression": "'/messages/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_repository_navigation_facet",
@ -1353,14 +1383,18 @@
"tool": "get_repository_navigation_facet",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 341,
"line": 357,
"target_calls": [
{
"method": "GET",
"path_expression": "f'/ports/projections/repository-navigation/facets/{facet_kind}/{facet_value}'"
"path_expression": "f'/ports/projections/repository-navigation/facets/{self._segment(facet_kind)}/{self._segment(facet_value)}'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded",
"runtime"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_risks",
@ -1368,14 +1402,17 @@
"tool": "get_risks",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 461,
"line": 477,
"target_calls": [
{
"method": "GET",
"path_expression": "'/progress/risks/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:get_state_summary",
@ -1383,14 +1420,17 @@
"tool": "get_state_summary",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 100,
"line": 116,
"target_calls": [
{
"method": "GET",
"path_expression": "'/state/summary/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:ingest_tpsc_tool",
@ -1398,14 +1438,17 @@
"tool": "ingest_tpsc_tool",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 448,
"line": 464,
"target_calls": [
{
"method": "POST",
"path_expression": "'/tpsc/ingest/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:list_capabilities",
@ -1413,14 +1456,17 @@
"tool": "list_capabilities",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 192,
"line": 208,
"target_calls": [
{
"method": "GET",
"path_expression": "'/capability-catalog/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:list_capability_requests",
@ -1428,14 +1474,17 @@
"tool": "list_capability_requests",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 261,
"line": 277,
"target_calls": [
{
"method": "GET",
"path_expression": "'/capability-requests/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:list_domain_repos",
@ -1443,14 +1492,17 @@
"tool": "list_domain_repos",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 309,
"line": 325,
"target_calls": [
{
"method": "GET",
"path_expression": "'/repos/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:list_domains",
@ -1458,14 +1510,17 @@
"tool": "list_domains",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 104,
"line": 120,
"target_calls": [
{
"method": "GET",
"path_expression": "'/domains/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:list_services",
@ -1473,14 +1528,17 @@
"tool": "list_services",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 431,
"line": 447,
"target_calls": [
{
"method": "GET",
"path_expression": "'/tpsc/catalog/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:mark_message_read",
@ -1488,14 +1546,17 @@
"tool": "mark_message_read",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 156,
"line": 172,
"target_calls": [
{
"method": "PATCH",
"path_expression": "f'/messages/{message_id}/read/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:query_repository_navigation",
@ -1503,14 +1564,18 @@
"tool": "query_repository_navigation",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 313,
"line": 329,
"target_calls": [
{
"method": "GET",
"path_expression": "'/ports/projections/repository-navigation/repositories'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded",
"runtime"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:query_workloads",
@ -1518,14 +1583,18 @@
"tool": "query_workloads",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 357,
"line": 373,
"target_calls": [
{
"method": "GET",
"path_expression": "'/ports/projections/workloads'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded",
"runtime"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:register_capability",
@ -1533,14 +1602,17 @@
"tool": "register_capability",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 169,
"line": 185,
"target_calls": [
{
"method": "POST",
"path_expression": "'/capability-catalog/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:register_repo",
@ -1548,14 +1620,17 @@
"tool": "register_repo",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 278,
"line": 294,
"target_calls": [
{
"method": "POST",
"path_expression": "'/repos/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:register_service",
@ -1563,14 +1638,17 @@
"tool": "register_service",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 406,
"line": 422,
"target_calls": [
{
"method": "POST",
"path_expression": "'/tpsc/catalog/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:reply_to_message",
@ -1578,14 +1656,17 @@
"tool": "reply_to_message",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 160,
"line": 176,
"target_calls": [
{
"method": "POST",
"path_expression": "f'/messages/{message_id}/reply/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:request_capability",
@ -1593,14 +1674,17 @@
"tool": "request_capability",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 205,
"line": 221,
"target_calls": [
{
"method": "POST",
"path_expression": "'/capability-requests/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:resolve_workload_reference",
@ -1608,14 +1692,18 @@
"tool": "resolve_workload_reference",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 379,
"line": 395,
"target_calls": [
{
"method": "GET",
"path_expression": "'/ports/projections/workloads/resolve'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded",
"runtime"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:send_message",
@ -1623,14 +1711,17 @@
"tool": "send_message",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 116,
"line": 132,
"target_calls": [
{
"method": "POST",
"path_expression": "'/messages/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:update_capability_request_status",
@ -1638,14 +1729,17 @@
"tool": "update_capability_request_status",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 248,
"line": 264,
"target_calls": [
{
"method": "PATCH",
"path_expression": "f'/capability-requests/{request_id}/status/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "mcp:update_repo_path",
@ -1653,14 +1747,17 @@
"tool": "update_repo_path",
"profile": "mcp-client",
"source": "hub_core/mcp/server.py",
"line": 303,
"line": 319,
"target_calls": [
{
"method": "POST",
"path_expression": "f'/repos/{repo_slug}/paths/'"
}
],
"current_gate": "per-invocation token provider available; host adoption required"
"backend_profiles": [
"embedded"
],
"current_gate": "per-invocation credential provider available; enforced runtime requires stdio credential file; embedded host admission required"
},
{
"id": "sdk:create_capability_catalog_router:GET:/capability-catalog/",