Public MCP Edge
Reference for the public machine-to-machine MCP surface
Public MCP Edge
Phase 13.5 keeps the public MCP edge as a real public execution surface while adding deployment parity across development, hosted, and local-tunnel backends. External clients can:
- discover public-safe agent entries with
ortho.agents.v1.list - invoke approved public agents with
ortho.agents.v1.invoke - read public-safe runtime posture with
ortho.system.v1.info - poll long-running public work with
tasks/getandtasks/result
The web host remains transport-thin. It authenticates, validates, and delegates into the canonical public runtime rather than acting as a second execution harness.
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/mcp | POST | Streamable HTTP MCP transport with JSON-RPC framing for initialize, tools/list, tools/call, tasks/get, and tasks/result |
/.well-known/oauth-protected-resource/mcp | GET | Protected Resource Metadata for the public MCP resource |
/.well-known/oauth-authorization-server/mcp | GET | Authorization Server Metadata for the public MCP resource |
Deployment parity
Phase 13.5 preserves one public MCP contract across all supported deployment backends:
developmentuses the app-hosted runtime directlylocal_tunnelforwards only already-admitted public calls into the local runtimehostedresolves the request into a tenant-local hosted runtime
Clients do not switch endpoints, tools, auth posture, or task semantics when the backend changes.
The only public-safe deployment distinction surfaced to callers is server.backendMode in
ortho.system.v1.info, which can be development, local_tunnel, or hosted.
Admission posture
/mcprequires OAuth bearer posture before any tool mapping, task lookup, or runtime dispatch.- Admission validates request schema, bearer presence, token validity, audience, origin, and scopes.
- External callers are represented as
ExternalClientsubjects at the boundary. - Public tool names stay namespaced as
ortho.*and map once onto the canonical flat internal capability surface. - Agent invoke scopes are binding-aware:
ortho.agents.v1.invokealways requiresortho.agents.invoke, plus any requested memory-tier scopes derived from the binding payload. - The public edge remains the only authority for auth, quota, rate-limit, and audit posture even
when the selected backend is
local_tunnelorhosted.
Phase 13.5 availability
tools/list returns only the intersection of caller scopes and current-phase-enabled public mappings.
Enabled tools
| Public name | Purpose | Required scope | Task support |
|---|---|---|---|
ortho.memory.v1.put | Append or supersede one source-local STM/LTM entry | tier-specific write scope | none |
ortho.memory.v1.get | Fetch one source-local STM/LTM entry | tier-specific read scope | none |
ortho.memory.v1.search | Search source-local STM/LTM entries | tier-specific read scope | none |
ortho.memory.v1.delete | Soft-delete one source-local STM/LTM entry | tier-specific delete scope | none |
ortho.memory.v1.compact | Run external-only summarize or extract_facts compaction | strategy-derived write scope | optional |
ortho.agents.v1.list | Discover public-safe agent catalog entries | ortho.system.read | none |
ortho.agents.v1.invoke | Invoke an approved public agent | ortho.agents.invoke plus any binding-derived memory scopes | optional |
ortho.system.v1.info | Read public-safe runtime metadata | ortho.system.read | none |
The tool list and request/response shapes remain the same across development, local_tunnel, and
hosted backends.
Public agents
Phase 13.4 continues to expose public-safe agent aliases instead of leaking internal runtime IDs.
Current catalog entries:
| Agent ID | Purpose | Input modes | Memory binding |
|---|---|---|---|
engineering.workflow | Structured engineering-orchestration requests | text, packet, json | supported (ltm read, stm write) |
Example ortho.agents.v1.list response:
{
"jsonrpc": "2.0",
"id": "rpc-list-agents-1",
"result": {
"agents": [
{
"agentId": "engineering.workflow",
"title": "Engineering Workflow",
"description": "A public-safe orchestration agent for structured engineering tasks.",
"inputModes": ["text", "packet", "json"],
"memoryBinding": {
"supported": true,
"readTiers": ["stm", "ltm"],
"writeTiers": ["stm"]
},
"execution": {
"taskSupport": "optional",
"asyncThreshold": "long_running_only"
}
}
]
}
}Task methods
When a public invoke is accepted into the async path, the edge returns a task handle and clients poll via the MCP task methods.
tasks/get
{
"jsonrpc": "2.0",
"id": "rpc-task-get-1",
"method": "tasks/get",
"params": {
"taskId": "550e8400-e29b-41d4-a716-446655440100"
}
}Example result:
{
"jsonrpc": "2.0",
"id": "rpc-task-get-1",
"result": {
"taskId": "550e8400-e29b-41d4-a716-446655440100",
"toolName": "ortho.agents.v1.invoke",
"subjectNamespace": "app:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"canonicalRunId": "550e8400-e29b-41d4-a716-446655440101",
"status": "running",
"submittedAt": "2026-03-14T00:00:00.000Z",
"updatedAt": "2026-03-14T00:00:01.000Z"
}
}tasks/result
{
"jsonrpc": "2.0",
"id": "rpc-task-result-1",
"method": "tasks/result",
"params": {
"taskId": "550e8400-e29b-41d4-a716-446655440100"
}
}Example terminal result:
{
"jsonrpc": "2.0",
"id": "rpc-task-result-1",
"result": {
"taskId": "550e8400-e29b-41d4-a716-446655440100",
"status": "completed",
"result": {
"runId": "550e8400-e29b-41d4-a716-446655440101",
"outputs": [
{
"type": "text",
"text": "Structured public result..."
}
]
}
}
}MCP examples
The examples below use a concrete namespace shape. Replace the bearer token and namespace with values derived for your client.
Authorization: Bearer eyJhbGciOi...
Namespace: app:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
Endpoint: POST /mcpinitialize
{
"jsonrpc": "2.0",
"id": "rpc-init-1",
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}{
"jsonrpc": "2.0",
"id": "rpc-init-1",
"result": {
"protocolVersion": "2025-11-25",
"serverInfo": {
"name": "Nous Public MCP",
"version": "0.0.1"
},
"capabilities": {
"tools": {
"listChanged": false
},
"tasks": {}
}
}
}tools/list
{
"jsonrpc": "2.0",
"id": "rpc-tools-1",
"method": "tools/list",
"params": {}
}Example result for a token with ortho.system.read but not ortho.agents.invoke:
{
"jsonrpc": "2.0",
"id": "rpc-tools-1",
"result": {
"tools": [
{
"name": "ortho.agents.v1.list",
"version": "1.0.0",
"description": "Public MCP tool ortho.agents.v1.list.",
"capabilities": ["external"],
"permissionScope": "external",
"inputSchema": {},
"outputSchema": {}
},
{
"name": "ortho.system.v1.info",
"version": "1.0.0",
"description": "Public MCP tool ortho.system.v1.info.",
"capabilities": ["external"],
"permissionScope": "external",
"inputSchema": {},
"outputSchema": {}
}
]
}
}ortho.agents.v1.invoke
{
"jsonrpc": "2.0",
"id": "rpc-invoke-1",
"method": "tools/call",
"params": {
"name": "ortho.agents.v1.invoke",
"arguments": {
"agentId": "engineering.workflow",
"input": {
"type": "text",
"text": "Summarize the current public invoke contract."
},
"memory": {
"namespace": "app:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"readTiers": ["ltm"],
"writeTiers": ["stm"]
},
"executionMode": "async",
"idempotencyKey": "invoke-engineering-workflow-001"
}
}
}Async acceptance example:
{
"jsonrpc": "2.0",
"id": "rpc-invoke-1",
"result": {
"mode": "task",
"task": {
"taskId": "550e8400-e29b-41d4-a716-446655440100",
"status": "queued",
"runId": "550e8400-e29b-41d4-a716-446655440101"
}
}
}Synchronous completion example for executionMode: "sync" when the run finishes within the sync threshold:
{
"jsonrpc": "2.0",
"id": "rpc-invoke-2",
"result": {
"mode": "completed",
"runId": "550e8400-e29b-41d4-a716-446655440102",
"outputs": [
{
"type": "text",
"text": "Public agents can return inline results when the request completes within the sync threshold."
}
]
}
}ortho.system.v1.info
{
"jsonrpc": "2.0",
"id": "rpc-system-1",
"method": "tools/call",
"params": {
"name": "ortho.system.v1.info",
"arguments": {}
}
}{
"jsonrpc": "2.0",
"id": "rpc-system-1",
"result": {
"server": {
"name": "Andre Hosted Nous",
"phase": "phase-13.5",
"backendMode": "hosted",
"protocolVersion": "2025-11-25"
},
"features": {
"publicAgents": true,
"publicSystemInfo": true,
"publicTasks": true,
"publicCompactAsync": true
},
"limits": {
"maxInvokeInputBytes": 8192,
"maxSearchTopK": 50,
"maxTaskPollWindowSeconds": 300
},
"quotas": {
"invokePerMinute": 10,
"compactPerMinute": 10
},
"tasks": {
"supportedMethods": ["tasks/get", "tasks/result"],
"toolSupport": {
"ortho.agents.v1.invoke": "optional",
"ortho.memory.v1.compact": "optional"
}
}
}
}The response shape stays the same across backends. Only public-safe deployment details such as
server.name, server.phase, and server.backendMode vary.
Carried-forward memory examples
ortho.memory.v1.put supersede with idempotencyKey
Use mode: "supersede" when the new record replaces an existing one. The gateway writes the new record, marks the predecessor as superseded, and treats the idempotencyKey as the replay key for safe retries.
{
"jsonrpc": "2.0",
"id": "rpc-put-1",
"method": "tools/call",
"params": {
"name": "ortho.memory.v1.put",
"arguments": {
"namespace": "app:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"tier": "stm",
"content": "Use Tuesday morning for supplier follow-up.",
"mode": "supersede",
"supersedesEntryId": "entry-previous",
"tags": ["crm", "follow-up"],
"metadata": {
"source": "public-mcp-example"
},
"idempotencyKey": "put-follow-up-002"
}
}
}ortho.memory.v1.compact with extract_facts
extract_facts is the carried-forward fact-extraction strategy. It derives durable LTM facts from the selected STM window while preserving the source-local public-memory boundary.
{
"jsonrpc": "2.0",
"id": "rpc-compact-1",
"method": "tools/call",
"params": {
"name": "ortho.memory.v1.compact",
"arguments": {
"namespace": "app:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"sourceTier": "stm",
"strategy": "extract_facts",
"maxEntries": 10,
"idempotencyKey": "compact-facts-001"
}
}
}Error behavior
Common public reject reasons in Phase 13.5:
| Reject reason | Meaning |
|---|---|
scope_insufficient | The bearer token does not include the scope required by the request or its memory binding posture. |
namespace_unauthorized | The request namespace or memory binding namespace does not match the namespace derived from the validated client identity. |
memory_binding_forbidden | The requested binding tiers are not allowed for the selected public agent. |
agent_not_available | The requested public agent alias is not allowlisted for this surface. |
task_not_found | The task does not exist for the requesting subject. |
task_not_ready | The task exists but its terminal result is not available yet. |
request_schema_invalid | The JSON-RPC request or tool arguments failed schema validation. |
source_quarantined | The source is quarantined or purged for durable mutation paths. |
quota_exceeded | The request exceeds the namespace + token quota window. |
rate_limited | The namespace + token has exceeded the allowed request rate for the tool. |
deployment_not_resolved | The public host did not map to a known development, hosted, or tunnel deployment target. |
tunnel_envelope_invalid | The forwarded tunnel request failed signature, binding, or expiry validation. |
tunnel_replay_detected | A tunnel nonce was already consumed and the request was rejected as a replay. |
Operator configuration
Phase 13.5 adds deployment-seeding environment variables for hosted and local-tunnel routing:
| Variable | Purpose |
|---|---|
NOUS_PUBLIC_BASE_URL | Base URL used by the OAuth discovery documents for the public MCP edge. |
NOUS_PUBLIC_MCP_HOSTED_BINDINGS_JSON | Seeded hosted tenant bindings used to map a public host or user handle to a hosted backend. |
NOUS_PUBLIC_MCP_TUNNEL_SESSIONS_JSON | Seeded tunnel sessions used to map a public host or user handle to a local-tunnel backend. |
See Configuration Schema for the accepted JSON shapes.
Runtime boundary
The public MCP edge is not a public-only business-logic stack. The host accepts HTTP and JSON-RPC traffic, the public gateway owns admission and audit posture, and admitted calls flow into the canonical gateway/runtime seam.
local_tunneldoes not expose a raw local MCP server; it forwards only already-admitted public calls into the local runtime.hostedresolves requests into tenant-local runtime state rather than proxying through shared mutable storage.- Long-running public work is projected through subject-scoped task records rather than a second status model.
- Phase 13.5 still does not add any public promoted-tier tool or public path into internal-only promoted storage.