doc(connections): Enterpret acceptance test against the generic MCP path

PAP-18519. The recorded answer to the question both connector skills ask
first -- does this vendor need a catalog entry at all -- for the vendor
Michael named as the acceptance case.

Reproduces Enterpret's public metadata from this runtime (no drift on any
stated fact, three additive observations), then exercises Paperclip's
client-resolution ladder against a loopback mirror of that metadata, on a
disposable self-hosted instance started for the pass and torn down after
it. No Enterpret credential exists, none was requested, no client was
registered there, no consent given, no tool called.

The three questions the ticket asked, answered with executed output:

- Discovery resolves completely from the 401 challenge alone, in two
  metadata fetches. The catalog definition should ship serverUrl only; a
  hard-coded endpoint pair would become authoritative and skip discovery
  for good.
- The ladder falls through to DCR, but the public-HTTPS condition the
  playbook names is not what decides it -- this authorization server
  never advertises a Client ID Metadata Document, so DCR is the only rung
  on any deployment. Asserted for both callback origins.
- Revoke is enforced entirely Paperclip-side. Zero revocation calls were
  made because there is nothing to call: revocation_endpoint is read
  nowhere in server/src and has no slot in the persisted oauth config.
  Scenario 8 cannot read verified for this provider.

The catalog-entry recommendation is yes, and the reason is governance
rather than connectivity. With no annotations to infer from, all ten
documented tools classify as read -- including run_graph_query and
execute_cypher_query, which execute operator-supplied queries against the
vendor's customer-feedback graph. The wizard shows the operator ten reads
and no changes.

The nine runbook scenarios are marked per deployment. Every row naming
Enterpret itself is not run, for one reason: no authorized credential.
Cloud is untested throughout.

The mirror server is embedded in the doc rather than added as a script,
so the skills package keeps its markdown_only trustLevel. Extracted and
booted from the published text to confirm it runs as written.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
nguyenm7andPaperclip committed 2026-09-23 23:50:46 +00:00
1 parent 1aeba0e6c2
commit 066a4e8019
2 files changed
+485

No files matched your search

+480
View File
@@ -0,0 +1,480 @@
# Enterpret MCP — acceptance test against the generic MCP-direct path
PAP-18519. Executed 23 September 2026 against Paperclip App commit
`1aeba0e6c2` on the `PAP-18464-connector-authoring-skills` branch, on a
disposable self-hosted instance started for this pass and torn down after it.
This is the acceptance test for the two connector skills shipped on PAP-18464:
Michael asked for an Enterpret connector, and both skills say to establish
first whether a catalog entry is needed at all.
**No Enterpret credential exists and none was requested.** Paperclip's connect
flow performs RFC 7591 dynamic client registration automatically and Enterpret's
`/register` is live, so pointing a connect flow at the live endpoint would have
created a client record at a vendor we have no relationship with. Everything
below is either an **unauthenticated public metadata read** of Enterpret, or a
run against a **loopback mirror** of Enterpret's published metadata. No client
was registered with Enterpret, no consent was given, no tool was called, and no
Enterpret data of any kind was read. No credential value appears in this file.
## 1. Provider metadata, reproduced
Read unauthenticated from this runtime on 23 September 2026, against DevRel's
readings of the same day. **No drift on any stated fact.**
| Fact | Reproduced |
| --- | --- |
| `POST https://wisdom-api.enterpret.com/server/mcp`, MCP `initialize`, no auth | `HTTP/2 401`, `server: uvicorn` |
| Challenge header | `Bearer realm="mcp", resource_metadata="https://wisdom-api.enterpret.com/server/mcp/.well-known/oauth-protected-resource", scope="mcp:read mcp:write"` — exact match |
| Trailing-slash form | `401`, empty `redirect_url` — no redirect, as stated |
| RFC 9728 doc | `resource` matches the endpoint; `authorization_servers: ["https://oauth.enterpret.com"]`; `scopes_supported: ["email","mcp:read","mcp:write"]` |
| RFC 8414 doc at `https://oauth.enterpret.com/.well-known/oauth-authorization-server` | `issuer` matches the issuer used to build the URL, so it is authoritative under the skill's Step 2 check. `/authorize`, `/token`, `/register`, `/introspect`, `/userinfo`; `jwks_uri` on Cognito `us-east-2_kLiRrPBis`; `code_challenge_methods_supported: ["S256"]`; `token_endpoint_auth_methods_supported: ["none"]`; `grant_types_supported: ["authorization_code","refresh_token"]` |
| `revocation_endpoint` | **absent**, confirmed — and absent from the OIDC document too |
| `/.well-known/openid-configuration` | `200` |
Three observations DevRel's list does not carry, all additive:
- Enterpret also serves the **RFC 9728 insertion form**
`https://wisdom-api.enterpret.com/.well-known/oauth-protected-resource/server/mcp`
and the bare origin form, both `200`, both returning the same document. Only
the challenge-named suffix form is actually needed — see §2.
- A plain `GET` on the MCP endpoint also returns `401` with the same challenge,
so discovery does not depend on issuing a JSON-RPC body.
- The OIDC document carries `service_documentation:
https://docs.enterpret.com/mcp` and `id_token_signing_alg_values_supported:
["RS256"]`, neither of which appears in the RFC 8414 document.
## 2. The discovery ladder, against a mirror
Two independent runs, the same shape both times.
**Deterministic run.** `server/src/__tests__/generic-mcp-connection.test.ts`,
`describe("a vendor-shaped MCP-direct provider (PAP-18519)")` — four tests, all
passing, 70/70 in the file, server typecheck clean. The fixture copies
Enterpret's documents field for field onto DNS-pinned IP literals: same
challenge header including `realm` and `scope`, same protected-resource
document, same RFC 8414 document including `token_endpoint_auth_methods_supported:
["none"]`, PKCE `S256`, and **no** `revocation_endpoint`.
**Live-instance run.** A disposable self-hosted instance
(`local_trusted` + `private`, embedded PostgreSQL, loopback `127.0.0.1:38519`,
no `PAPERCLIP_PUBLIC_URL`) plus a loopback mirror server on
`127.0.0.1:38520` serving the same documents. Source for the mirror is in §6.
### Does discovery resolve from the 401 challenge alone?
**Yes, completely.** The operator supplies a URL and a name; nothing else.
From the live instance, `POST /api/companies/{c}/tools/apps/connect` with
`{link, name}` returned `HTTP 201` and this persisted `config.oauth`:
```json
{
"issuer": "http://127.0.0.1:38520",
"scopes": ["mcp:read", "mcp:write"],
"resource": "http://127.0.0.1:38520/server/mcp",
"authorizationUrl": "http://127.0.0.1:38520/authorize",
"tokenUrl": "http://127.0.0.1:38520/token",
"registrationUrl": "http://127.0.0.1:38520/register",
"metadataUrl": "http://127.0.0.1:38520/server/mcp/.well-known/oauth-protected-resource",
"codeChallengeMethodsSupported": ["S256"],
"tokenEndpointAuthMethodsSupported": ["none"],
"clientIdMetadataDocumentSupported": false
}
```
The provider's own request log shows the whole resolution costing **two**
metadata fetches per connection, in order, and nothing else:
```text
GET /server/mcp/.well-known/oauth-protected-resource
GET /.well-known/oauth-authorization-server
```
The insertion form Enterpret also serves was never fetched: the challenge names
the suffix form, and a named `resource_metadata` short-circuits Paperclip's own
candidate list (`discoverOAuthEndpoints`, `server/src/services/tool-access.ts`).
Because the issuer is pathless, RFC 8414 well-known insertion never applies and
the origin form is the only candidate — so the pathful-issuer ambiguity that
bites other vendors does not arise here.
**Consequence for the catalog definition: ship `serverUrl` only.** A definition
that also shipped an `authorizationEndpoint`/`tokenEndpoint` pair would make
those endpoints authoritative and skip discovery entirely, for good
(`oauthEndpointsForConnection` rung 1), including overriding endpoints a prior
discovery had persisted. There is nothing to gain here and a stale-endpoint
failure mode to acquire.
One behaviour worth naming, because it is not what the documents suggest: the
challenge's `scope="mcp:read mcp:write"` **wins over** the protected-resource
document's `scopes_supported`, so the `email` scope the resource advertises is
never requested. That narrowing is the provider's own, not a reviewed Paperclip
allowlist.
### What does the ladder do with no public HTTPS origin?
**It falls through to DCR, as the playbook says — but the public-origin
condition is not what decides it for this vendor.**
The playbook frames the Client ID Metadata Document rung as requiring a public
HTTPS `PAPERCLIP_PUBLIC_URL`. Enterpret's authorization server never advertises
`client_id_metadata_document_supported` at all, so the rung is missing on the
*server* side. Asserted both ways in the deterministic run: a loopback callback
and a public HTTPS callback with a resolvable metadata host both produce
`registrationSource: "dcr"`. On the live instance, with no public origin
configured at all, the wizard returned:
```json
{
"kind": "oauth",
"issuer": "http://127.0.0.1:38520",
"resource": "http://127.0.0.1:38520/server/mcp",
"registrationSource": "dcr"
}
```
and the authorization URL carried `code_challenge_method=S256`, a
`code_challenge`, `resource=<the MCP endpoint>` (RFC 8707) and
`scope=mcp:read mcp:write`. Registration sent `token_endpoint_auth_method:
"none"`, `grant_types: ["authorization_code","refresh_token"]`,
`response_types: ["code"]` and the instance's own callback — matching what
Enterpret's `token_endpoint_auth_methods_supported: ["none"]` requires.
The round trip completed: `status: active, enabled: true, healthStatus: ok`,
`clientRegistrationSource: "dcr"`, credentials as
`oauth.access_token` / `oauth.refresh_token` secret refs, never in the config
JSON.
**So for a Paperclip-owned Enterpret connection there is no OAuth app to
register in advance, on any deployment.** Neither Paperclip ID nor Paperclip
Connect participates.
### What happens at revoke time with no `revocation_endpoint`?
This is the one that decides runbook scenario 8, and the answer is split.
**Paperclip's half works.** Same connection, same agent, same tool, before and
after `DELETE /api/tool-connections/{id}`:
```text
before granted agent -> get_graph_schema (selected at finish) allowed=True allow_profile
before granted agent -> run_graph_query (not selected) allowed=False deny_default
after granted agent -> get_graph_schema allowed=False deny_disabled_connection
```
**The provider's half does not happen at all.** Across the whole pass the
provider received **zero** requests to any revocation or introspection path —
because Paperclip never looks for one. `revocation_endpoint` is read nowhere in
`server/src`, and the persisted `config.oauth` has no slot for it among its 30
keys. Revocation is enforced entirely at the Paperclip boundary; the access
token stays valid at the vendor until it expires on its own.
This is the same posture already recorded for Railway
(`RAILWAY.md:139`), so it is a known class rather than an Enterpret surprise.
But it means **scenario 8 can never read `verified` end to end for Enterpret**.
The honest label is "Paperclip-side enforced, provider-side residual", and the
connector doc must tell the operator to revoke in the Enterpret dashboard as a
second, manual step. If that is unacceptable, the fix is an RFC 7009 client in
the broker plus a vendor that advertises the endpoint — Enterpret advertises
neither, so this is a provider gap, not a Paperclip one.
## 3. Does this need a catalog entry at all?
**Not for connectivity. Yes for governance.** Both halves are executed, not
argued.
### What an operator gets today, with no code change
Both operator-facing entry points work against the mirror on the live instance,
with zero Paperclip code:
**Paste a config** — `POST /api/companies/{c}/tools/mcp/import-json`:
```json
{"drafts":[{"name":"enterpret","transport":"mcp_remote","status":"draft",
"config":{"url":"…/server/mcp"},"credentialRefs":[],"credentialFields":[],"warnings":[]}]}
```
**Connect your own MCP server** — `POST /api/companies/{c}/tools/apps/connect`
→ `201`, discovery as §2, DCR, consent, active connection, catalog refreshed,
all ten documented tools discovered, wizard `finish` binding one tool to one
agent with `defaultAction: "deny"`.
### What is missing, and it is not cosmetic
**The adverse finding, reproduced on both the deterministic run and the live
instance.** With no `annotations` on the tools — and Enterpret's live
annotations have never been observed — Paperclip infers risk from names alone,
and files **all ten as `read`**:
```text
tool riskLevel destructive
execute_cypher_query read False
find_user_quote read False
get_graph_schema read False
get_organization_details read False
get_query_examples read False
get_schema read False
run_graph_query read False
search_graph_fields read False
search_graph_values read False
search_knowledge_graph read False
```
`run_graph_query` and its legacy alias `execute_cypher_query` execute
operator-supplied queries against Enterpret's customer-feedback graph, and the
provider itself advertises an `mcp:write` scope. The wizard shows the operator
ten reads and no changes. That is the single strongest argument for a curated
entry: **curated method labels and a reviewed risk tier are the fix, and there
is no other place to put them.**
Everything else the generic path cannot supply, field for field against
`packages/shared/src/app-definitions/notion.json`:
| What a catalog entry adds | What the pasted URL gives instead |
| --- | --- |
| `slug` — one stable identity | `applicationKey: "app-gallery:link:<uuid>"`, synthesized per connection. Two operators pasting the same URL get two unrelated applications and nothing knows they are the same vendor. |
| `name`, `description`, `branding.logoUrl` | the operator's typed name; `description: "Connected app at <url>"`; no logo |
| `categories`, `featured` | no store visibility at all |
| `riskTier` | nothing; per-tool risk inferred from names, wrongly, as above |
| curated method labels / `guidanceMd` | raw tool names and the provider's own descriptions |
| reviewed scope allowlist | the provider's own challenge `scope`, unreviewed |
| `requiredResourceFilters` | none |
| `ownershipModes` | no gate on whether Paperclip may auto-register at this vendor |
| `redirectConstraints` | no fail-fast; a bad origin surfaces as a vendor-side error |
| quarantine of newly advertised tools | `quarantineNewEntries: false` and `unverifiedServer: true` are set together for a pasted URL — see **F1** in the connect-agent-tools verification log: a server that advertises a new tool after approval has it auto-allowed on the next refresh, whatever its risk tier |
**Recommendation.** Build the catalog entry, and say plainly in the proposal
that its value is curation rather than connectivity. It should ship `serverUrl`
only, `ownershipModes: ["dcr"]` (Enterpret needs no pre-provisioned app),
`redirectConstraints` probed separately, a reviewed scope list, and explicit
per-method risk for `run_graph_query` / `execute_cypher_query`. F1 should be
fixed on its own merits regardless — a curated entry quarantines new tools, and
that is precisely the protection a pasted URL lacks.
## 4. The nine production-validation scenarios
Runbook matrix at `CONNECTOR-PLAYBOOK.md:1445-1455`. **Every row that names
Enterpret itself is `not run`, for one reason: there is no authorized
credential, and registration, consent and tool calls are out of bounds.** The
mirror result is recorded beside it, because the mirror exercises Paperclip's
code against Enterpret's advertised shape and that is what Paperclip can be held
to.
Environment for every mirror result: disposable self-hosted instance, commit
`1aeba0e6c2`, `local_trusted` + `private`, loopback bind, method key
`connect-your-own-mcp-server` (pasted URL, discovered OAuth).
### Self-hosted, same machine
| # | Scenario | Against Enterpret | Against the mirror |
| --- | --- | --- | --- |
| 1 | Setup and consent | **not run** — consent needs an authorized Enterpret login | **pass** — import-json → connect → authorize → callback → finish, no code change |
| 2 | Authentication | **not run** — no credential | **pass** — DCR, PKCE S256, RFC 8707 resource, `iss` validated, tokens vaulted as secret refs |
| 3 | Catalog and configuration | **not run** — an authenticated `tools/list` has never been observed, so the live tool set is documentation only | **pass with an adverse finding** — 10/10 discovered, selections persist; all 10 misclassified `read` (§3) |
| 4 | Allowed execution | **not run** — a tool call against the live server is out of bounds | **partial** — policy allows the selected tool (`allow / allow_profile`); no gateway call executed this pass. The equivalent agent-run execution is proven for this code path in the connect-agent-tools verification log, scenario 4 |
| 5 | Denied execution | **not run** | **pass** — unselected tool `deny / deny_default`; archived connection `deny / deny_disabled_connection` |
| 6 | Runtime delivery | **not run** | **not run this pass** — no agent heartbeat run was started. Proven for the identical generic path in the connect-agent-tools verification log, scenario 6 |
| 7 | Refresh and recovery | **not run** | **not run this pass** — provider-down and token-refresh cycles were not exercised. Token refresh in particular is untested for this shape and should be covered before the entry ships |
| 8 | Revoke and reconnect | **not applicable as a pass** — Enterpret advertises no `revocation_endpoint`, so no provider-side revocation exists to verify | **partial, and it cannot become a pass** — Paperclip-side denial proven; zero revocation calls made, none possible; reconnect not exercised |
| 9 | Activity and secret handling | **not run** | **pass** — 13 activity rows (`tool_app.connected`, `tool_app.oauth_connected`, `tool_app.finished`, `tool_connection.archived`, …); synthetic token canary found 0 times across `tools/connections`, `activity`, `secrets`, `tool-connections/{id}`, `tool-connections/{id}/activity` and the whole instance file tree, while the non-secret `client_id` is visible as expected |
### Self-hosted, server / VPS
**untested**, with one exception that carries. The only axis that differs from
same-machine is the callback origin: public HTTPS instead of loopback. The
deterministic run asserts both origins against this vendor shape and gets
`registrationSource: "dcr"` either way, because the authorization server never
advertises a Client ID Metadata Document. So **scenario 2's client-resolution
answer is established for server/VPS**; scenarios 1 and 3–9 were not run there
and must not be inferred from the same-machine column.
### Cloud
**untested.** No Cloud instance was in reach and no Cloud evidence exists. Do
not infer any row from the self-hosted columns.
## 5. What is still needed, and who owns it
To move rows 1–7 and 9 from `not run` to a real result against Enterpret,
someone must supply **one authorized Enterpret workspace identity**, in either
of the two forms Enterpret documents:
- an Enterpret account that may complete the OAuth consent screen for a
Paperclip-owned tenant (scopes `mcp:read mcp:write`, and `email` if the
reviewed allowlist keeps it); or
- an Enterpret-issued Bearer auth token — dashboard → Settings → Enterpret MCP →
Generate — placed in the `Authorization` header, which Enterpret expires after
six months, so the connector doc must carry a rotation note.
Authorizing dynamic client registration at `oauth.enterpret.com` is part of that
grant and is not implied by either form. **DevRel owns the handoff.** Nothing in
this pass requested or created an Enterpret credential.
Scenario 8 does not become a pass with any credential. It needs Enterpret to
advertise a `revocation_endpoint` and Paperclip to gain an RFC 7009 client.
## 6. Reproducing this
The deterministic half needs nothing but the repository:
```bash
pnpm vitest run server/src/__tests__/generic-mcp-connection.test.ts -t "PAP-18519"
```
The live half needs a disposable instance and the mirror below. Start the
instance with `paperclipai onboard -y --data-dir <scratch> --no-install-service`,
pin `server.port` and `database.embeddedPostgresPort` in
`<scratch>/instances/default/config.json` to free ports, then
`paperclipai run --data-dir <scratch> --force`. Strip inherited `PAPERCLIP_*`
environment variables or the CLI will send another instance's credentials to the
new one. Kill it by the pid in `runtime-info.json` when finished.
The mirror serves Enterpret's published documents with the two origins rewritten
to loopback. It contacts nothing, stores nothing, and logs header *keys* only —
so no credential can reach its log.
```js
// mirror.mjs — node mirror.mjs 38520
import { createServer } from "node:http";
import { randomUUID } from "node:crypto";
const port = Number(process.argv[2] ?? 38520);
const ORIGIN = `http://127.0.0.1:${port}`;
const MCP_PATH = "/server/mcp";
const MCP_URL = `${ORIGIN}${MCP_PATH}`;
const CHALLENGE =
`Bearer realm="mcp", resource_metadata="${MCP_URL}/.well-known/oauth-protected-resource", scope="mcp:read mcp:write"`;
const PROTECTED_RESOURCE = {
resource: MCP_URL,
authorization_servers: [ORIGIN],
scopes_supported: ["email", "mcp:read", "mcp:write"],
};
// Verbatim Enterpret RFC 8414 field set. No revocation_endpoint: that absence
// is the point of the fixture, so do not add one.
const AUTHORIZATION_SERVER = {
authorization_endpoint: `${ORIGIN}/authorize`,
code_challenge_methods_supported: ["S256"],
grant_types_supported: ["authorization_code", "refresh_token"],
introspection_endpoint: `${ORIGIN}/introspect`,
issuer: ORIGIN,
jwks_uri: `${ORIGIN}/jwks.json`,
registration_endpoint: `${ORIGIN}/register`,
response_types_supported: ["code"],
scopes_supported: ["email", "mcp:read", "mcp:write"],
token_endpoint: `${ORIGIN}/token`,
token_endpoint_auth_methods_supported: ["none"],
userinfo_endpoint: `${ORIGIN}/userinfo`,
};
// Enterpret's documented tool list plus the three legacy aliases it still
// serves. No annotations, because none have ever been observed.
const TOOLS = [
"get_organization_details", "get_graph_schema", "get_query_examples",
"search_graph_fields", "search_graph_values", "run_graph_query",
"find_user_quote", "get_schema", "execute_cypher_query",
"search_knowledge_graph",
].map((name) => ({ name, description: name, inputSchema: { type: "object", properties: {} } }));
const requestLog = [];
const issuedCodes = new Set();
const issuedTokens = new Set();
function send(res, status, payload, headers = {}) {
res.writeHead(status, { "content-type": "application/json", ...headers });
res.end(JSON.stringify(payload));
}
createServer(async (req, res) => {
const url = new URL(req.url, ORIGIN);
let raw = "";
for await (const chunk of req) raw += chunk;
requestLog.push({
at: new Date().toISOString(), method: req.method, path: url.pathname,
headerKeys: Object.keys(req.headers).sort(),
authorizationPresent: Boolean(req.headers.authorization),
});
if (url.pathname === "/__requests") return send(res, 200, requestLog);
// Enterpret answers all three RFC 9728 forms with the same document.
if (url.pathname === `${MCP_PATH}/.well-known/oauth-protected-resource`
|| url.pathname === `/.well-known/oauth-protected-resource${MCP_PATH}`
|| url.pathname === "/.well-known/oauth-protected-resource") {
return send(res, 200, PROTECTED_RESOURCE);
}
if (url.pathname === "/.well-known/oauth-authorization-server"
|| url.pathname === "/.well-known/openid-configuration") {
return send(res, 200, AUTHORIZATION_SERVER);
}
if (url.pathname === "/register" && req.method === "POST") {
const body = JSON.parse(raw || "{}");
return send(res, 201, {
client_id: `mirror-client-${randomUUID()}`,
redirect_uris: body.redirect_uris, grant_types: body.grant_types,
response_types: body.response_types,
token_endpoint_auth_method: body.token_endpoint_auth_method,
client_name: body.client_name,
});
}
// Stands in for the consent screen: no human, so it approves at once.
if (url.pathname === "/authorize") {
const code = `mirror-code-${randomUUID()}`;
issuedCodes.add(code);
const redirect = new URL(url.searchParams.get("redirect_uri"));
redirect.searchParams.set("code", code);
redirect.searchParams.set("state", url.searchParams.get("state") ?? "");
redirect.searchParams.set("iss", ORIGIN);
res.writeHead(302, { location: redirect.toString() });
return res.end();
}
if (url.pathname === "/token" && req.method === "POST") {
const body = new URLSearchParams(raw);
if (body.get("grant_type") === "authorization_code" && !issuedCodes.has(body.get("code"))) {
return send(res, 400, { error: "invalid_grant" });
}
const token = `mirror-access-${randomUUID()}`;
issuedTokens.add(token);
return send(res, 200, {
access_token: token, refresh_token: `mirror-refresh-${randomUUID()}`,
expires_in: 3600, token_type: "Bearer", scope: "mcp:read mcp:write",
});
}
if (url.pathname === MCP_PATH) {
const supplied = (req.headers.authorization ?? "").replace(/^Bearer\s+/i, "");
if (!issuedTokens.has(supplied)) {
return send(res, 401,
{ error: "invalid_token", error_description: "Missing or invalid access token" },
{ "www-authenticate": CHALLENGE });
}
const rpc = JSON.parse(raw || "{}");
if (rpc.method === "initialize") {
return send(res, 200, { jsonrpc: "2.0", id: rpc.id, result: {
protocolVersion: "2025-06-18", capabilities: { tools: {} },
serverInfo: { name: "enterpret-shape-mirror", version: "1.0.0" } } });
}
if (rpc.method === "tools/list") {
return send(res, 200, { jsonrpc: "2.0", id: rpc.id, result: { tools: TOOLS } });
}
return send(res, 200, { jsonrpc: "2.0", id: rpc.id ?? null, result: {
content: [{ type: "text", text: "mirror acknowledged. This server holds no data." }] } });
}
return send(res, 404, { error: "not_found" });
}).listen(port, "127.0.0.1", () => console.log(`mirror listening on ${MCP_URL}`));
```
The redaction check in scenario 9 works because the mirror prefixes its tokens
`mirror-access-` / `mirror-refresh-`: those prefixes are a synthetic canary that
can be grepped safely across the API and the instance tree.
## 7. Service involvement
Neither Paperclip ID nor Paperclip Connect participates. DCR is instance-local:
each instance registers its own public client at the vendor and uses its own
`/api/tools/oauth/callback`. Cloud and self-hosted use the same path; only the
hostname inside the redirect URI differs.
+5
View File
@@ -177,6 +177,11 @@ not own durable tokens.
Apps v2: research, connection-type selection, OAuth/API-key/generated-URL
setup, encrypted credential handling, branding, implementation, browser and
live-provider testing, verification, and PR submission.
- [Enterpret acceptance test](./ENTERPRET-ACCEPTANCE.md) is the worked example
of applying the two documents above to one vendor with no authorized
credential: what a pasted URL resolves on its own, what a catalog entry would
add on top, and which of the nine production-validation scenarios can be
answered at all without the vendor's consent.
- [Vercel Connect operator guide](./VERCEL-CONNECT.md) documents the optional
external credential source, deployment flags, runtime resolution, recovery,
and smoke requirements.