hermoso 0.1.139 → 0.1.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mcp/http.mjs +29 -4
- package/mcp/tools.mjs +539 -394
- package/package.json +1 -1
package/mcp/http.mjs
CHANGED
|
@@ -32,12 +32,37 @@ export function mountRemoteMcp(app, { verifyBearer, publicBaseUrl } = {}) {
|
|
|
32
32
|
// RFC 9728 protected-resource metadata — tells Claude.ai where to get a token. (Authorization-server metadata
|
|
33
33
|
// is served by the auth provider itself, e.g. Firebase/your IdP.) Scopes match the AS metadata + minted token
|
|
34
34
|
// (mcp/oauth.mjs): hermoso.research / hermoso.generate.
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// ── SERVED AT BOTH URL FORMS, FROM ONE HANDLER (2026-08-20) ──────────────────────────────────────────────────
|
|
36
|
+
// The resource identifier is `${BASE}/mcp`, which HAS a path component — so RFC 9728 §3.1 derives its metadata
|
|
37
|
+
// URL by INSERTING the well-known suffix before that path ("any terminating slash (/) following the host
|
|
38
|
+
// component MUST be removed before inserting /.well-known/ and the well-known URI path suffix between the host
|
|
39
|
+
// component and the path"), i.e. /.well-known/oauth-protected-resource/mcp. We served only the ROOT form, and
|
|
40
|
+
// that was not a cosmetic gap — it was a DEAD END for any client that does not use our WWW-Authenticate hint,
|
|
41
|
+
// because RFC 9728 §3.3 applies a DIFFERENT validation rule depending on how the client arrived:
|
|
42
|
+
// • via the `resource_metadata` hint → `resource` MUST equal the URL used to reach the resource server
|
|
43
|
+
// (`${BASE}/mcp`). Our document satisfies this, which is why Claude connects today.
|
|
44
|
+
// • by CONSTRUCTING the well-known URL → `resource` MUST equal the identifier the suffix was inserted into.
|
|
45
|
+
// From the ROOT form that identifier is `${BASE}` (no /mcp) while our document says `${BASE}/mcp`, and the
|
|
46
|
+
// rule is "the data contained in the response MUST NOT be used". So a hint-less client had nowhere to land:
|
|
47
|
+
// the suffixed URL 404'd, and the root URL it fell back to failed validation.
|
|
48
|
+
// Serving BOTH is explicitly sanctioned — the MCP spec's fallback order is suffixed-then-root ("Serve metadata
|
|
49
|
+
// at a well-known URI … either: At the path of the server's MCP endpoint … or At the root"). ONE handler, never
|
|
50
|
+
// a second copy of the document: two copies drift, and a stale one is worse than a 404.
|
|
51
|
+
//
|
|
52
|
+
// DELIBERATELY ASYMMETRIC with /.well-known/oauth-authorization-server (mcp/oauth.mjs), which is NOT aliased:
|
|
53
|
+
// its `issuer` is BASE with NO path component, so RFC 8414 §3.1 puts its metadata at the root URL, and §3.3
|
|
54
|
+
// would force a client to REJECT the identical document served under a /mcp suffix (it would have constructed
|
|
55
|
+
// that URL from issuer identifier `${BASE}/mcp`, which is not what the document says). The 404 there is
|
|
56
|
+
// CORRECT and is what lets a probing client fall through cleanly. Alias it only if `issuer` ever grows a path.
|
|
57
|
+
const MCP_PATH = '/mcp'; // the resource's path component — app.all(MCP_PATH) below
|
|
58
|
+
const protectedResourceMetadata = (req, res) => res.json({
|
|
59
|
+
resource: `${BASE}${MCP_PATH}`,
|
|
37
60
|
authorization_servers: [process.env.HEIST_OAUTH_ISSUER].filter(Boolean),
|
|
38
61
|
scopes_supported: ['hermoso.research', 'hermoso.generate'],
|
|
39
62
|
bearer_methods_supported: ['header'],
|
|
40
|
-
})
|
|
63
|
+
});
|
|
64
|
+
app.get('/.well-known/oauth-protected-resource', protectedResourceMetadata);
|
|
65
|
+
app.get(`/.well-known/oauth-protected-resource${MCP_PATH}`, protectedResourceMetadata);
|
|
41
66
|
|
|
42
67
|
// Per-session Streamable-HTTP transports. Each authenticated session gets its own McpServer with the same tools.
|
|
43
68
|
//
|
|
@@ -131,7 +156,7 @@ export function mountRemoteMcp(app, { verifyBearer, publicBaseUrl } = {}) {
|
|
|
131
156
|
await transport.handleRequest(req, res, req.body);
|
|
132
157
|
}
|
|
133
158
|
|
|
134
|
-
app.all(
|
|
159
|
+
app.all(MCP_PATH, async (req, res) => {
|
|
135
160
|
const auth = req.headers.authorization || '';
|
|
136
161
|
const token = auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
137
162
|
const user = token ? await verifyBearer(token).catch(() => null) : null;
|