@privos_ai/app-server 0.1.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/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/app-descriptor.d.ts +39 -0
- package/dist/app-descriptor.d.ts.map +1 -0
- package/dist/app-descriptor.js +71 -0
- package/dist/app-descriptor.js.map +1 -0
- package/dist/auth/user-token.d.ts +44 -0
- package/dist/auth/user-token.d.ts.map +1 -0
- package/dist/auth/user-token.js +108 -0
- package/dist/auth/user-token.js.map +1 -0
- package/dist/context/identity-assertions.d.ts +18 -0
- package/dist/context/identity-assertions.d.ts.map +1 -0
- package/dist/context/identity-assertions.js +37 -0
- package/dist/context/identity-assertions.js.map +1 -0
- package/dist/context/identity-mode.d.ts +3 -0
- package/dist/context/identity-mode.d.ts.map +1 -0
- package/dist/context/identity-mode.js +6 -0
- package/dist/context/identity-mode.js.map +1 -0
- package/dist/context/tool-call-context.d.ts +29 -0
- package/dist/context/tool-call-context.d.ts.map +1 -0
- package/dist/context/tool-call-context.js +2 -0
- package/dist/context/tool-call-context.js.map +1 -0
- package/dist/direct/express-router.d.ts +35 -0
- package/dist/direct/express-router.d.ts.map +1 -0
- package/dist/direct/express-router.js +148 -0
- package/dist/direct/express-router.js.map +1 -0
- package/dist/direct/http-ingress.d.ts +84 -0
- package/dist/direct/http-ingress.d.ts.map +1 -0
- package/dist/direct/http-ingress.js +124 -0
- package/dist/direct/http-ingress.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/dispatch.d.ts +7 -0
- package/dist/protocol/dispatch.d.ts.map +1 -0
- package/dist/protocol/dispatch.js +78 -0
- package/dist/protocol/dispatch.js.map +1 -0
- package/dist/protocol/errors.d.ts +16 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +84 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/logging.d.ts +16 -0
- package/dist/protocol/logging.d.ts.map +1 -0
- package/dist/protocol/logging.js +18 -0
- package/dist/protocol/logging.js.map +1 -0
- package/dist/protocol/types.d.ts +54 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +7 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/relay/message-adapter.d.ts +9 -0
- package/dist/relay/message-adapter.d.ts.map +1 -0
- package/dist/relay/message-adapter.js +32 -0
- package/dist/relay/message-adapter.js.map +1 -0
- package/dist/relay/relay-client.d.ts +63 -0
- package/dist/relay/relay-client.d.ts.map +1 -0
- package/dist/relay/relay-client.js +447 -0
- package/dist/relay/relay-client.js.map +1 -0
- package/dist/runtime.d.ts +104 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +351 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PrivOS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @privos_ai/app-server
|
|
2
|
+
|
|
3
|
+
Business-agnostic PrivOS MCP App server runtime for **Direct HTTP** and **Relay WebSocket**.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @privos_ai/app-server
|
|
9
|
+
# peer: express
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick start (Direct router only)
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import express from 'express';
|
|
16
|
+
import { createDirectRouter } from '@privos_ai/app-server';
|
|
17
|
+
|
|
18
|
+
const app = express();
|
|
19
|
+
app.get('/health', (_req, res) => res.json({ ok: true }));
|
|
20
|
+
app.use(
|
|
21
|
+
createDirectRouter({
|
|
22
|
+
descriptor: { id: 'com.example.app', name: 'Example', version: '1.0.0' },
|
|
23
|
+
handler: async (request) => {
|
|
24
|
+
if (request.method === 'tools/list') return { tools: [] };
|
|
25
|
+
throw Object.assign(new Error('Method not found'), { code: -32601 });
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start (HTTP ingress helper)
|
|
32
|
+
|
|
33
|
+
For Relay+HTTP dual mode or a minimal Direct binary — health/ready/listen included:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
startHttpIngress,
|
|
38
|
+
resolveHttpIngressListen,
|
|
39
|
+
connectRelay,
|
|
40
|
+
} from '@privos_ai/app-server';
|
|
41
|
+
|
|
42
|
+
const listen = resolveHttpIngressListen({ defaultPort: 8080 });
|
|
43
|
+
const shared = {
|
|
44
|
+
descriptor: { id: 'com.example.app', name: 'Example', version: '1.0.0' },
|
|
45
|
+
handler: async (request) => {
|
|
46
|
+
if (request.method === 'tools/list') return { tools: [] };
|
|
47
|
+
throw Object.assign(new Error('Method not found'), { code: -32601 });
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (listen.enabled) {
|
|
52
|
+
await startHttpIngress({
|
|
53
|
+
...shared,
|
|
54
|
+
port: listen.port,
|
|
55
|
+
publicUrl: listen.publicUrl,
|
|
56
|
+
ready: {
|
|
57
|
+
check: async () => ({ ok: true, body: { /* app checks */ } }),
|
|
58
|
+
},
|
|
59
|
+
// Optional: mount /ui, static assets, webhooks before listen
|
|
60
|
+
configure: async (app) => {
|
|
61
|
+
// app.use('/ui', …)
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
connectRelay({ ...shared, privosUrl, clientId, clientSecret });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Env: `HTTP_INGRESS`, `HTTP_PORT` / `PORT`, `PUBLIC_URL`.
|
|
70
|
+
|
|
71
|
+
Runtime owns `initialize`, `notifications/*`, and the configured primary UI resource.
|
|
72
|
+
Your handler owns `tools/list`, `tools/call`, and custom resources.
|
|
73
|
+
|
|
74
|
+
## Auth
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { verifyPrivosUser } from '@privos_ai/app-server/auth';
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Scripts
|
|
81
|
+
|
|
82
|
+
- `npm run build` — emit `dist/` (JS + `.d.ts`)
|
|
83
|
+
- `npm test` — Vitest (in-process mock Hub only)
|
|
84
|
+
- `npm run typecheck`
|
|
85
|
+
- `npm pack` — runs `prepack` build; package contains `dist/` only
|
|
86
|
+
- `npx tsx scripts/probe-relay-contract.ts` — staging Phase 0 probe (requires credentials; exit 2 when blocked)
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
2
|
+
export declare const MCP_UI_MIME = "text/html;profile=mcp-app";
|
|
3
|
+
export interface AppDescriptor {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
homepage?: string;
|
|
10
|
+
author?: {
|
|
11
|
+
name: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
website?: string;
|
|
14
|
+
};
|
|
15
|
+
scopes?: readonly string[];
|
|
16
|
+
/** Direct manifest icon path/URL (e.g. `/public/icon.svg`). */
|
|
17
|
+
manifestIcon?: string;
|
|
18
|
+
/** Pairing / initialize icon (often a data URI for relay). */
|
|
19
|
+
relayIcon?: string;
|
|
20
|
+
/** Extra capabilities merged under runtime-owned defaults; may not override reserved keys. */
|
|
21
|
+
capabilities?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export declare function validateDescriptorCapabilities(capabilities: Record<string, unknown> | undefined): void;
|
|
24
|
+
export declare function buildInitializeResult(descriptor: AppDescriptor, options?: {
|
|
25
|
+
uiEnabled?: boolean;
|
|
26
|
+
}): {
|
|
27
|
+
protocolVersion: string;
|
|
28
|
+
capabilities: Record<string, unknown>;
|
|
29
|
+
serverInfo: Record<string, unknown>;
|
|
30
|
+
};
|
|
31
|
+
export declare function buildManifestJson(descriptor: AppDescriptor): Record<string, unknown>;
|
|
32
|
+
export declare function buildPairingMetadata(descriptor: AppDescriptor): {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
version: string;
|
|
36
|
+
icon?: string;
|
|
37
|
+
scopes?: string[];
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=app-descriptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-descriptor.d.ts","sourceRoot":"","sources":["../src/app-descriptor.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD,eAAO,MAAM,WAAW,8BAA8B,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAID,wBAAgB,8BAA8B,CAC7C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC/C,IAAI,CASN;AAED,wBAAgB,qBAAqB,CACpC,UAAU,EAAE,aAAa,EACzB,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/B;IACF,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CA6BA;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBpF;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,aAAa,GAAG;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,CAQA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const MCP_PROTOCOL_VERSION = '2025-03-26';
|
|
2
|
+
export const MCP_UI_MIME = 'text/html;profile=mcp-app';
|
|
3
|
+
const RESERVED_CAPABILITY_KEYS = new Set(['tools', 'extensions']);
|
|
4
|
+
export function validateDescriptorCapabilities(capabilities) {
|
|
5
|
+
if (!capabilities)
|
|
6
|
+
return;
|
|
7
|
+
for (const key of Object.keys(capabilities)) {
|
|
8
|
+
if (RESERVED_CAPABILITY_KEYS.has(key)) {
|
|
9
|
+
throw new Error(`descriptor.capabilities must not override reserved capability "${key}"`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function buildInitializeResult(descriptor, options) {
|
|
14
|
+
validateDescriptorCapabilities(descriptor.capabilities);
|
|
15
|
+
const capabilities = {
|
|
16
|
+
tools: {},
|
|
17
|
+
...(options?.uiEnabled
|
|
18
|
+
? {
|
|
19
|
+
extensions: {
|
|
20
|
+
'io.modelcontextprotocol/ui': {
|
|
21
|
+
mimeTypes: [MCP_UI_MIME],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
: {}),
|
|
26
|
+
...(descriptor.capabilities ?? {}),
|
|
27
|
+
};
|
|
28
|
+
const serverInfo = {
|
|
29
|
+
name: descriptor.title || descriptor.name || descriptor.id,
|
|
30
|
+
version: descriptor.version,
|
|
31
|
+
};
|
|
32
|
+
if (descriptor.relayIcon)
|
|
33
|
+
serverInfo.icon = descriptor.relayIcon;
|
|
34
|
+
if (descriptor.scopes?.length)
|
|
35
|
+
serverInfo.scopes = [...descriptor.scopes];
|
|
36
|
+
return {
|
|
37
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
38
|
+
capabilities,
|
|
39
|
+
serverInfo,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function buildManifestJson(descriptor) {
|
|
43
|
+
return {
|
|
44
|
+
name: descriptor.id,
|
|
45
|
+
version: descriptor.version,
|
|
46
|
+
title: descriptor.title || descriptor.name,
|
|
47
|
+
description: descriptor.description ?? '',
|
|
48
|
+
...(descriptor.manifestIcon ? { icon: descriptor.manifestIcon } : {}),
|
|
49
|
+
...(descriptor.author
|
|
50
|
+
? {
|
|
51
|
+
author: {
|
|
52
|
+
name: descriptor.author.name,
|
|
53
|
+
...(descriptor.author.email ? { email: descriptor.author.email } : {}),
|
|
54
|
+
...(descriptor.author.website ? { website: descriptor.author.website } : {}),
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
...(descriptor.homepage ? { homepage: descriptor.homepage } : {}),
|
|
59
|
+
...(descriptor.scopes ? { scopes: [...descriptor.scopes] } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function buildPairingMetadata(descriptor) {
|
|
63
|
+
return {
|
|
64
|
+
name: descriptor.title || descriptor.name || descriptor.id,
|
|
65
|
+
description: descriptor.description ?? '',
|
|
66
|
+
version: descriptor.version,
|
|
67
|
+
...(descriptor.relayIcon ? { icon: descriptor.relayIcon } : {}),
|
|
68
|
+
...(descriptor.scopes?.length ? { scopes: [...descriptor.scopes] } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=app-descriptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-descriptor.js","sourceRoot":"","sources":["../src/app-descriptor.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAC;AAEjD,MAAM,CAAC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAmBvD,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;AAElE,MAAM,UAAU,8BAA8B,CAC7C,YAAiD;IAEjD,IAAI,CAAC,YAAY;QAAE,OAAO;IAC1B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7C,IAAI,wBAAwB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CACd,kEAAkE,GAAG,GAAG,CACxE,CAAC;QACH,CAAC;IACF,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACpC,UAAyB,EACzB,OAAiC;IAMjC,8BAA8B,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAExD,MAAM,YAAY,GAA4B;QAC7C,KAAK,EAAE,EAAE;QACT,GAAG,CAAC,OAAO,EAAE,SAAS;YACrB,CAAC,CAAC;gBACA,UAAU,EAAE;oBACX,4BAA4B,EAAE;wBAC7B,SAAS,EAAE,CAAC,WAAW,CAAC;qBACxB;iBACD;aACD;YACF,CAAC,CAAC,EAAE,CAAC;QACN,GAAG,CAAC,UAAU,CAAC,YAAY,IAAI,EAAE,CAAC;KAClC,CAAC;IAEF,MAAM,UAAU,GAA4B;QAC3C,IAAI,EAAE,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,EAAE;QAC1D,OAAO,EAAE,UAAU,CAAC,OAAO;KAC3B,CAAC;IACF,IAAI,UAAU,CAAC,SAAS;QAAE,UAAU,CAAC,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC;IACjE,IAAI,UAAU,CAAC,MAAM,EAAE,MAAM;QAAE,UAAU,CAAC,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAE1E,OAAO;QACN,eAAe,EAAE,oBAAoB;QACrC,YAAY;QACZ,UAAU;KACV,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAyB;IAC1D,OAAO;QACN,IAAI,EAAE,UAAU,CAAC,EAAE;QACnB,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,KAAK,EAAE,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,IAAI;QAC1C,WAAW,EAAE,UAAU,CAAC,WAAW,IAAI,EAAE;QACzC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,GAAG,CAAC,UAAU,CAAC,MAAM;YACpB,CAAC,CAAC;gBACA,MAAM,EAAE;oBACP,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI;oBAC5B,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtE,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5E;aACD;YACF,CAAC,CAAC,EAAE,CAAC;QACN,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,UAAyB;IAO7D,OAAO;QACN,IAAI,EAAE,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,EAAE;QAC1D,WAAW,EAAE,UAAU,CAAC,WAAW,IAAI,EAAE;QACzC,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type JSONWebKeySet } from 'jose';
|
|
2
|
+
import type { VerifiedActor } from '../context/tool-call-context.js';
|
|
3
|
+
export interface AuthOptions {
|
|
4
|
+
jwksUrl: string | URL | (() => string | URL);
|
|
5
|
+
audience: string | readonly string[] | (() => string | readonly string[]);
|
|
6
|
+
issuer?: string | readonly string[];
|
|
7
|
+
clockToleranceSeconds?: number;
|
|
8
|
+
/** Mapped to jose `createRemoteJWKSet` `cacheMaxAge` (ms). */
|
|
9
|
+
jwksCacheTtlMs?: number;
|
|
10
|
+
fetchTimeoutMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Test/injection escape hatch — when set, skips remote JWKS fetch.
|
|
13
|
+
* Production code should leave this unset.
|
|
14
|
+
*/
|
|
15
|
+
localJwks?: JSONWebKeySet;
|
|
16
|
+
}
|
|
17
|
+
export interface CallerCredential {
|
|
18
|
+
token: string;
|
|
19
|
+
/** e.g. Direct `X-MCP-User-Id` — cross-checked against verified `sub`. */
|
|
20
|
+
assertedUserId?: string;
|
|
21
|
+
source: 'direct-header' | 'relay-metadata' | 'custom';
|
|
22
|
+
}
|
|
23
|
+
export type VerifyUserTokenResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
actor: VerifiedActor;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: 'missing' | 'invalid';
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
/** Test helper — verify against an in-memory JWKS (no network). */
|
|
32
|
+
export declare function createLocalJwksVerifier(jwks: JSONWebKeySet): (protectedHeader?: import("jose").JWSHeaderParameters, token?: import("jose").FlattenedJWSInput) => Promise<import("jose").KeyLike>;
|
|
33
|
+
export declare function clearJwksCache(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Verify a Hub-signed RS256 user JWT via JWKS.
|
|
36
|
+
* Algorithm is fixed to RS256 — HS256 / none are rejected by jose verify options.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyUserToken(token: string | undefined, options: AuthOptions, assertedUserId?: string): Promise<VerifyUserTokenResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Convenience for app-owned HTTP routes (non-MCP).
|
|
41
|
+
* Throws on missing/invalid token — mirrors the former template `verifyPrivosUser`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function verifyPrivosUser(authHeader: string | undefined, options: AuthOptions): Promise<VerifiedActor>;
|
|
44
|
+
//# sourceMappingURL=user-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-token.d.ts","sourceRoot":"","sources":["../../src/auth/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,KAAK,aAAa,EAElB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC;IAC7C,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,CAAC,MAAM,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC;IAC1E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACpC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,eAAe,GAAG,gBAAgB,GAAG,QAAQ,CAAC;CACtD;AAED,MAAM,MAAM,qBAAqB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAuCjE,mEAAmE;AACnE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,uIAE1D;AAED,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAwBD;;;GAGG;AACH,wBAAsB,eAAe,CACpC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,WAAW,EACpB,cAAc,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,qBAAqB,CAAC,CAgChC;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,WAAW,GAClB,OAAO,CAAC,aAAa,CAAC,CAQxB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createLocalJWKSet, createRemoteJWKSet, jwtVerify, } from 'jose';
|
|
2
|
+
const jwksCache = new Map();
|
|
3
|
+
function resolveUrl(value) {
|
|
4
|
+
const raw = typeof value === 'function' ? value() : value;
|
|
5
|
+
return raw instanceof URL ? raw : new URL(raw);
|
|
6
|
+
}
|
|
7
|
+
function resolveAudience(value) {
|
|
8
|
+
const raw = typeof value === 'function' ? value() : value;
|
|
9
|
+
if (typeof raw === 'string')
|
|
10
|
+
return raw;
|
|
11
|
+
return [...raw];
|
|
12
|
+
}
|
|
13
|
+
function resolveIssuer(value) {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (typeof value === 'string')
|
|
17
|
+
return value;
|
|
18
|
+
return [...value];
|
|
19
|
+
}
|
|
20
|
+
function getJwks(options) {
|
|
21
|
+
const url = resolveUrl(options.jwksUrl);
|
|
22
|
+
const cacheKey = `${url.toString()}|${options.jwksCacheTtlMs ?? ''}|${options.fetchTimeoutMs ?? ''}`;
|
|
23
|
+
let jwks = jwksCache.get(cacheKey);
|
|
24
|
+
if (!jwks) {
|
|
25
|
+
jwks = createRemoteJWKSet(url, {
|
|
26
|
+
timeoutDuration: options.fetchTimeoutMs ?? 5_000,
|
|
27
|
+
...(options.jwksCacheTtlMs !== undefined
|
|
28
|
+
? { cacheMaxAge: options.jwksCacheTtlMs }
|
|
29
|
+
: {}),
|
|
30
|
+
});
|
|
31
|
+
jwksCache.set(cacheKey, jwks);
|
|
32
|
+
}
|
|
33
|
+
return jwks;
|
|
34
|
+
}
|
|
35
|
+
/** Test helper — verify against an in-memory JWKS (no network). */
|
|
36
|
+
export function createLocalJwksVerifier(jwks) {
|
|
37
|
+
return createLocalJWKSet(jwks);
|
|
38
|
+
}
|
|
39
|
+
export function clearJwksCache() {
|
|
40
|
+
jwksCache.clear();
|
|
41
|
+
}
|
|
42
|
+
function getVerifier(options) {
|
|
43
|
+
if (options.localJwks) {
|
|
44
|
+
return createLocalJWKSet(options.localJwks);
|
|
45
|
+
}
|
|
46
|
+
return getJwks(options);
|
|
47
|
+
}
|
|
48
|
+
function actorFromPayload(payload) {
|
|
49
|
+
if (!payload.sub) {
|
|
50
|
+
throw new Error('JWT missing sub claim');
|
|
51
|
+
}
|
|
52
|
+
const username = typeof payload.preferred_username === 'string' ? payload.preferred_username : undefined;
|
|
53
|
+
const roomId = typeof payload.rid === 'string' ? payload.rid : undefined;
|
|
54
|
+
return {
|
|
55
|
+
userId: payload.sub,
|
|
56
|
+
...(username !== undefined ? { username } : {}),
|
|
57
|
+
...(roomId !== undefined ? { roomId } : {}),
|
|
58
|
+
claims: { ...payload },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Verify a Hub-signed RS256 user JWT via JWKS.
|
|
63
|
+
* Algorithm is fixed to RS256 — HS256 / none are rejected by jose verify options.
|
|
64
|
+
*/
|
|
65
|
+
export async function verifyUserToken(token, options, assertedUserId) {
|
|
66
|
+
if (!token) {
|
|
67
|
+
return { ok: false, reason: 'missing', message: 'Missing caller token' };
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const audience = resolveAudience(options.audience);
|
|
71
|
+
const issuer = resolveIssuer(options.issuer);
|
|
72
|
+
const { payload } = await jwtVerify(token, getVerifier(options), {
|
|
73
|
+
audience,
|
|
74
|
+
algorithms: ['RS256'],
|
|
75
|
+
clockTolerance: options.clockToleranceSeconds ?? 30,
|
|
76
|
+
...(issuer ? { issuer } : {}),
|
|
77
|
+
});
|
|
78
|
+
const actor = actorFromPayload(payload);
|
|
79
|
+
if (assertedUserId !== undefined && assertedUserId !== '' && assertedUserId !== actor.userId) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: 'invalid',
|
|
83
|
+
message: 'X-MCP-User-Id does not match verified token subject',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { ok: true, actor };
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
reason: 'invalid',
|
|
92
|
+
message: err instanceof Error ? err.message : 'Token verification failed',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Convenience for app-owned HTTP routes (non-MCP).
|
|
98
|
+
* Throws on missing/invalid token — mirrors the former template `verifyPrivosUser`.
|
|
99
|
+
*/
|
|
100
|
+
export async function verifyPrivosUser(authHeader, options) {
|
|
101
|
+
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice('Bearer '.length).trim() : undefined;
|
|
102
|
+
const result = await verifyUserToken(token, options);
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
throw new Error(result.message);
|
|
105
|
+
}
|
|
106
|
+
return result.actor;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=user-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-token.js","sourceRoot":"","sources":["../../src/auth/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,GAIT,MAAM,MAAM,CAAC;AA8Bd,MAAM,SAAS,GAAG,IAAI,GAAG,EAAiD,CAAC;AAE3E,SAAS,UAAU,CAAC,KAA0C;IAC7D,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1D,OAAO,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,eAAe,CACvB,KAAsE;IAEtE,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1D,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACxC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AACjB,CAAC;AAED,SAAS,aAAa,CAAC,KAA6C;IACnE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,OAAO,CAAC,OAAoB;IACpC,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,IAAI,OAAO,CAAC,cAAc,IAAI,EAAE,IAAI,OAAO,CAAC,cAAc,IAAI,EAAE,EAAE,CAAC;IACrG,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,IAAI,GAAG,kBAAkB,CAAC,GAAG,EAAE;YAC9B,eAAe,EAAE,OAAO,CAAC,cAAc,IAAI,KAAK;YAChD,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,SAAS;gBACvC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,cAAc,EAAE;gBACzC,CAAC,CAAC,EAAE,CAAC;SACN,CAAC,CAAC;QACH,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,uBAAuB,CAAC,IAAmB;IAC1D,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,cAAc;IAC7B,SAAS,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,WAAW,CAAC,OAAoB;IACxC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACvB,OAAO,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAmB;IAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,QAAQ,GACb,OAAO,OAAO,CAAC,kBAAkB,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,MAAM,MAAM,GAAG,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACzE,OAAO;QACN,MAAM,EAAE,OAAO,CAAC,GAAG;QACnB,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,MAAM,EAAE,EAAE,GAAG,OAAO,EAA6B;KACjD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,KAAyB,EACzB,OAAoB,EACpB,cAAuB;IAEvB,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAC1E,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE;YAChE,QAAQ;YACR,UAAU,EAAE,CAAC,OAAO,CAAC;YACrB,cAAc,EAAE,OAAO,CAAC,qBAAqB,IAAI,EAAE;YACnD,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7B,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,EAAE,IAAI,cAAc,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;YAC9F,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,qDAAqD;aAC9D,CAAC;QACH,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO;YACN,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B;SACzE,CAAC;IACH,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,UAA8B,EAC9B,OAAoB;IAEpB,MAAM,KAAK,GACV,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3F,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IdentityEnforcementMode } from './identity-mode.js';
|
|
2
|
+
import type { ToolCallContext, VerifiedActor } from './tool-call-context.js';
|
|
3
|
+
export declare class IdentityAssertionError extends Error {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
constructor(code: string, message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pure helper — does not read tool arguments.
|
|
9
|
+
* App decides whether a call requires an actor, then calls this.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertActorAvailable(context: ToolCallContext, mode: IdentityEnforcementMode): VerifiedActor;
|
|
12
|
+
/**
|
|
13
|
+
* Pure helper — `legacyId` must be extracted by the app and passed explicitly.
|
|
14
|
+
* Runtime never inspects MCP tool arguments.
|
|
15
|
+
* Mismatch rejects in both observe and enforce modes.
|
|
16
|
+
*/
|
|
17
|
+
export declare function assertActorMatchesLegacyId(actor: VerifiedActor, legacyId: string | undefined | null): void;
|
|
18
|
+
//# sourceMappingURL=identity-assertions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-assertions.d.ts","sourceRoot":"","sources":["../../src/context/identity-assertions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE7E,qBAAa,sBAAuB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAKzC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,uBAAuB,GAC3B,aAAa,CAuBf;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACzC,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GACjC,IAAI,CAQN"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class IdentityAssertionError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'IdentityAssertionError';
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Pure helper — does not read tool arguments.
|
|
11
|
+
* App decides whether a call requires an actor, then calls this.
|
|
12
|
+
*/
|
|
13
|
+
export function assertActorAvailable(context, mode) {
|
|
14
|
+
if (context.identityState === 'verified' && context.actor) {
|
|
15
|
+
return context.actor;
|
|
16
|
+
}
|
|
17
|
+
if (context.identityState === 'invalid') {
|
|
18
|
+
throw new IdentityAssertionError('IDENTITY_INVALID', 'Caller identity token is invalid');
|
|
19
|
+
}
|
|
20
|
+
if (mode === 'enforce') {
|
|
21
|
+
throw new IdentityAssertionError('IDENTITY_REQUIRED', 'Verified caller identity is required');
|
|
22
|
+
}
|
|
23
|
+
throw new IdentityAssertionError('IDENTITY_MISSING', 'Verified caller identity is missing');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Pure helper — `legacyId` must be extracted by the app and passed explicitly.
|
|
27
|
+
* Runtime never inspects MCP tool arguments.
|
|
28
|
+
* Mismatch rejects in both observe and enforce modes.
|
|
29
|
+
*/
|
|
30
|
+
export function assertActorMatchesLegacyId(actor, legacyId) {
|
|
31
|
+
if (legacyId == null || legacyId === '')
|
|
32
|
+
return;
|
|
33
|
+
if (actor.userId !== legacyId) {
|
|
34
|
+
throw new IdentityAssertionError('IDENTITY_MISMATCH', 'Verified actor does not match legacy user id');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=identity-assertions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-assertions.js","sourceRoot":"","sources":["../../src/context/identity-assertions.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACvC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACxC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;CACD;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CACnC,OAAwB,EACxB,IAA6B;IAE7B,IAAI,OAAO,CAAC,aAAa,KAAK,UAAU,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAC3D,OAAO,OAAO,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACzC,MAAM,IAAI,sBAAsB,CAC/B,kBAAkB,EAClB,kCAAkC,CAClC,CAAC;IACH,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,sBAAsB,CAC/B,mBAAmB,EACnB,sCAAsC,CACtC,CAAC;IACH,CAAC;IAED,MAAM,IAAI,sBAAsB,CAC/B,kBAAkB,EAClB,qCAAqC,CACrC,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACzC,KAAoB,EACpB,QAAmC;IAEnC,IAAI,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO;IAChD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,sBAAsB,CAC/B,mBAAmB,EACnB,8CAA8C,CAC9C,CAAC;IACH,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-mode.d.ts","sourceRoot":"","sources":["../../src/context/identity-mode.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,SAAS,CAAC;AAE5D,wBAAgB,4BAA4B,CAC3C,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,GAAE,uBAAmC,GAC3C,uBAAuB,CAGzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-mode.js","sourceRoot":"","sources":["../../src/context/identity-mode.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,4BAA4B,CAC3C,KAAyB,EACzB,WAAoC,SAAS;IAE7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7D,OAAO,QAAQ,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface VerifiedActor {
|
|
2
|
+
/** JWT `sub` — stable opaque user id. */
|
|
3
|
+
userId: string;
|
|
4
|
+
/** JWT `preferred_username` when present — display only. */
|
|
5
|
+
username?: string;
|
|
6
|
+
/** JWT `rid` when present. */
|
|
7
|
+
roomId?: string;
|
|
8
|
+
/** Raw claims subset safe for app policy (no token string). */
|
|
9
|
+
claims: Readonly<Record<string, unknown>>;
|
|
10
|
+
}
|
|
11
|
+
export type IdentityState = 'verified' | 'missing' | 'invalid';
|
|
12
|
+
export interface ToolCallContext {
|
|
13
|
+
transport: 'direct' | 'relay';
|
|
14
|
+
requestId?: string | number | null;
|
|
15
|
+
actor?: VerifiedActor;
|
|
16
|
+
roomId?: string;
|
|
17
|
+
appId?: string;
|
|
18
|
+
traceId?: string;
|
|
19
|
+
identityState: IdentityState;
|
|
20
|
+
/**
|
|
21
|
+
* Scope for duplicate in-flight JSON-RPC id detection.
|
|
22
|
+
* Direct: MCP-Session-Id or per-request ephemeral id.
|
|
23
|
+
* Relay: WebSocket connection generation.
|
|
24
|
+
*/
|
|
25
|
+
sessionScope: string;
|
|
26
|
+
/** Aborted when the runtime request timeout fires. Handlers should honor when possible. */
|
|
27
|
+
signal?: AbortSignal;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=tool-call-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-call-context.d.ts","sourceRoot":"","sources":["../../src/context/tool-call-context.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC7B,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1C;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,aAAa,CAAC;IAC7B;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-call-context.js","sourceRoot":"","sources":["../../src/context/tool-call-context.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Router, type Request } from 'express';
|
|
2
|
+
import { type AppDescriptor } from '../app-descriptor.js';
|
|
3
|
+
import type { AuthOptions } from '../auth/user-token.js';
|
|
4
|
+
import { AppServerRuntime, extractDirectCallerCredential, type AppErrorMapper, type AppMcpHandler, type AppServerRuntimeOptions, type CallerCredentialExtractor, type RuntimeLimits, type UiResourceProvider } from '../runtime.js';
|
|
5
|
+
export interface DirectRouterOptions {
|
|
6
|
+
descriptor: AppDescriptor | (() => AppDescriptor | Promise<AppDescriptor>);
|
|
7
|
+
handler: AppMcpHandler;
|
|
8
|
+
ui?: UiResourceProvider;
|
|
9
|
+
auth?: AuthOptions;
|
|
10
|
+
mapAppError?: AppErrorMapper;
|
|
11
|
+
limits?: RuntimeLimits;
|
|
12
|
+
logger?: AppServerRuntimeOptions['logger'];
|
|
13
|
+
/** Default `/mcp`. */
|
|
14
|
+
mcpPath?: string;
|
|
15
|
+
/** Default `/.well-known/mcp/manifest.json`. */
|
|
16
|
+
manifestPath?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Extra CORS allowed headers. Identity/protocol headers are always included
|
|
19
|
+
* and cannot be removed by omitting them here.
|
|
20
|
+
*/
|
|
21
|
+
corsAllowHeaders?: string[];
|
|
22
|
+
/** Override credential extraction. Must not read body/arguments. */
|
|
23
|
+
extractCallerCredential?: CallerCredentialExtractor<{
|
|
24
|
+
headers: Request['headers'];
|
|
25
|
+
}>;
|
|
26
|
+
runtime?: AppServerRuntime;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Composable Express Router for Direct MCP transport.
|
|
30
|
+
* Mount with `app.use(createDirectRouter(opts))` — does not create or listen on an app,
|
|
31
|
+
* and does not own `/ui`, health, ready, or register routes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createDirectRouter(options: DirectRouterOptions): Router;
|
|
34
|
+
export { AppServerRuntime, extractDirectCallerCredential };
|
|
35
|
+
//# sourceMappingURL=express-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express-router.d.ts","sourceRoot":"","sources":["../../src/direct/express-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,EAAoC,MAAM,SAAS,CAAC;AAGjF,OAAO,EAEN,KAAK,aAAa,EAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAOzD,OAAO,EACN,gBAAgB,EAGhB,6BAA6B,EAE7B,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,aAAa,GAAG,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IAC3E,OAAO,EAAE,aAAa,CAAC;IACvB,EAAE,CAAC,EAAE,kBAAkB,CAAC;IACxB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,MAAM,CAAC,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC3C,sBAAsB;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,oEAAoE;IACpE,uBAAuB,CAAC,EAAE,yBAAyB,CAAC;QACnD,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;KAC5B,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAUD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAgFvE;AAyFD,OAAO,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,CAAC"}
|