@corbits/mcp 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -112,7 +112,7 @@ mountMcpDiscovery(mcpRoutes, {
112
112
 
113
113
  Mount `mcpRoutes` on the hub app under `/api/tenants/:tenantId`, behind the hub's auth and tenant middleware.
114
114
 
115
- `POST /api/tenants/:tenantId/mcp/discover` with `{ url, credentialId? }` returns `{ data: { serverInfo, tools } }`. `url` must be https; plain-http loopback is refused unless the host sets `allowLoopback: true` for local development. `credentialId` names a tenant credential whose secret is sent as a bearer; a credential holding `MCP_NO_TOKEN_SENTINEL` from `@corbits/credential-http` sends no `authorization` header. Errors: 400 for a bad body or URL, 404 for an unknown credential or one that is not active or has expired, 422 when the server fails discovery, the secret is not a valid header value, or the request would leave the credential's origin. A 422 carries only this package's own messages and never the upstream HTTP status; any other failure reads as a generic handshake error, so no response or `onError` text quotes the secret. `requireGrant` is the host's own grant middleware for this route.
115
+ `POST /api/tenants/:tenantId/mcp/discover` with `{ url, credentialId? }` returns `{ data: { serverInfo, tools } }`. `url` must be https; plain-http loopback is refused unless the host sets `allowLoopback: true` for local development. `credentialId` names a tenant credential whose secret is sent as a bearer; the credential's type must be `api_key` or `oauth_token`, and any other type reads as not found. An `oauth_token` credential's secret is its current access token, which [`@corbits/oauth-core`](https://github.com/corbitsdev/corbits-oauth-core)'s refresher renews in place, so nothing here refreshes it, and one past its `expiresAt` or marked `error` reads as not found until it is renewed or the user signs in again; a credential holding `MCP_NO_TOKEN_SENTINEL` from `@corbits/credential-http` sends no `authorization` header. Errors: 400 for a bad body or URL, 404 for an unknown credential or one that is not active or has expired, 422 when the server fails discovery, the secret is not a valid header value, or the request would leave the credential's origin. A 422 carries only this package's own messages and never the upstream HTTP status; any other failure reads as a generic handshake error, so no response or `onError` text quotes the secret. `requireGrant` is the host's own grant middleware for this route.
116
116
 
117
117
  When a secret is sent, the fetch is pinned to the origin of the credential's provider `apiBaseUrl`; with no credential or a keyless one, to the URL's origin. Redirects are always refused, so the secret never leaves that origin. Each discovery request times out after 30 seconds.
118
118
 
@@ -163,6 +163,10 @@ Each catalog entry becomes a tool named `<handle>.<tool>`, with the remote `inpu
163
163
 
164
164
  `handle` is the credential handle the host binds this server's token to. The bundle resolves it from the runtime `credentials` capability and expects an `http` credential, such as the one `@corbits/credential-http`'s MCP provider returns. The package manifest declares the `mcp-server` credential for this.
165
165
 
166
+ #### OAuth servers
167
+
168
+ A host records how a server authenticates in the credential's `metadata.mcp.auth`: `"none"` (the keyless sentinel), `"token"` (a pasted `api_key`) or `"oauth"` (an `oauth_token` login through `@corbits/oauth-core`, using `discoverMcpLoginEntry` and `mcpClientConfig` above). This package does not read the value; it reads the credential's type, so pass the `oauth_token` credential's id as `credentialId`. At run time the credential's provider shapes the handle from the credential's current secret, so a refreshed token reaches running agents without any change to the bundle.
169
+
166
170
  ### Grant access
167
171
 
168
172
  Grant the agent's principal `tool:deepwiki.*` for the whole server, or `tool:deepwiki.<tool>` per tool, with `allow`, `ask` or `deny`.
@@ -34,8 +34,8 @@ export type McpCredential = {
34
34
  /**
35
35
  * Read a tenant credential's decrypted secret and the origin it is pinned to.
36
36
  * Scoped by tenant so a credential id from another tenant reads as absent,
37
- * not as a secret; a revoked, errored or expired credential reads as absent
38
- * too.
37
+ * not as a secret; a revoked, errored or expired credential, or one that holds no bearer
38
+ * token (a certificate, say), reads as absent too.
39
39
  */
40
40
  export declare function readCredential(opts: {
41
41
  readonly db: DB["db"];
@@ -13,17 +13,24 @@ const DiscoverBody = type({
13
13
  url: "string > 0",
14
14
  "credentialId?": "string > 0",
15
15
  });
16
+ /**
17
+ * Credential types whose secret is a bearer token. An `oauth_token` row's
18
+ * secret is the current access token, which `@corbits/oauth-core`'s refresher
19
+ * rewrites in place, so it is read here like any other and never refreshed.
20
+ */
21
+ const BEARER_CREDENTIAL_TYPES = ["api_key", "oauth_token"];
16
22
  /**
17
23
  * Read a tenant credential's decrypted secret and the origin it is pinned to.
18
24
  * Scoped by tenant so a credential id from another tenant reads as absent,
19
- * not as a secret; a revoked, errored or expired credential reads as absent
20
- * too.
25
+ * not as a secret; a revoked, errored or expired credential, or one that holds no bearer
26
+ * token (a certificate, say), reads as absent too.
21
27
  */
22
28
  export async function readCredential(opts) {
23
29
  const [row] = await opts.db
24
30
  .select({
25
31
  id: credential.id,
26
32
  secret: credential.secret,
33
+ type: credential.type,
27
34
  status: credential.status,
28
35
  expiresAt: credential.expiresAt,
29
36
  apiBaseUrl: provider.apiBaseUrl,
@@ -33,6 +40,7 @@ export async function readCredential(opts) {
33
40
  .where(and(eq(credential.id, opts.credentialId), eq(credential.tenantId, opts.tenantId)))
34
41
  .limit(1);
35
42
  if (row === undefined ||
43
+ !BEARER_CREDENTIAL_TYPES.includes(row.type) ||
36
44
  row.status !== "active" ||
37
45
  (row.expiresAt !== null && row.expiresAt.getTime() <= Date.now())) {
38
46
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corbits/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP client tool package for Interchange: streamable HTTP, per-tool grants, OAuth.",
5
5
  "keywords": [
6
6
  "corbits",
@@ -55,7 +55,7 @@
55
55
  "check": "bun run typecheck && bun run lint && bun run format:check && bun run test"
56
56
  },
57
57
  "dependencies": {
58
- "@corbits/oauth-core": "^0.2.0",
58
+ "@corbits/oauth-core": "^0.3.0",
59
59
  "@intx/types": "^0.4.0",
60
60
  "arktype": "^2.2.3"
61
61
  },