@markmnl/fmsg-mcp 0.1.3 → 0.2.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +78 -22
  3. package/ROADMAP.md +52 -0
  4. package/SECURITY.md +72 -0
  5. package/dist/auth.d.ts +14 -6
  6. package/dist/auth.js +115 -49
  7. package/dist/client/client.d.ts +34 -24
  8. package/dist/client/client.js +152 -105
  9. package/dist/client/errors.d.ts +19 -0
  10. package/dist/client/errors.js +51 -0
  11. package/dist/client/index.d.ts +1 -0
  12. package/dist/client/message-id.d.ts +1 -0
  13. package/dist/client/message-id.js +7 -0
  14. package/dist/client/stream.d.ts +12 -0
  15. package/dist/client/stream.js +83 -0
  16. package/dist/client/token-provider.d.ts +27 -0
  17. package/dist/client/token-provider.js +54 -0
  18. package/dist/client/types.d.ts +8 -4
  19. package/dist/client/url.d.ts +4 -0
  20. package/dist/client/url.js +35 -0
  21. package/dist/client/ws.d.ts +2 -1
  22. package/dist/client/ws.js +9 -3
  23. package/dist/config.d.ts +7 -3
  24. package/dist/config.js +15 -6
  25. package/dist/context.d.ts +6 -3
  26. package/dist/context.js +9 -2
  27. package/dist/errors.d.ts +1 -1
  28. package/dist/errors.js +18 -26
  29. package/dist/http.d.ts +4 -3
  30. package/dist/http.js +158 -23
  31. package/dist/index.js +27 -11
  32. package/dist/instructions.js +10 -5
  33. package/dist/oauth/config.d.ts +11 -0
  34. package/dist/oauth/config.js +46 -0
  35. package/dist/oauth/errors.d.ts +18 -0
  36. package/dist/oauth/errors.js +40 -0
  37. package/dist/oauth/issuer.d.ts +31 -0
  38. package/dist/oauth/issuer.js +185 -0
  39. package/dist/oauth/provider.d.ts +23 -0
  40. package/dist/oauth/provider.js +95 -0
  41. package/dist/oauth/scopes.d.ts +6 -0
  42. package/dist/oauth/scopes.js +31 -0
  43. package/dist/render.d.ts +6 -1
  44. package/dist/render.js +27 -11
  45. package/dist/resources.js +18 -9
  46. package/dist/server.js +2 -0
  47. package/dist/thread.js +15 -14
  48. package/dist/tools/common.js +8 -3
  49. package/dist/tools/identity.js +1 -1
  50. package/dist/tools/list.js +3 -3
  51. package/dist/tools/read.js +26 -43
  52. package/dist/tools/save.d.ts +2 -0
  53. package/dist/tools/save.js +84 -0
  54. package/dist/tools/send.js +9 -13
  55. package/dist/tools/wait.js +14 -5
  56. package/dist/wait.d.ts +15 -1
  57. package/dist/wait.js +232 -84
  58. package/docs/http-deployment.md +53 -0
  59. package/docs/oauth.md +163 -0
  60. package/docs/token-providers.md +94 -0
  61. package/package.json +8 -1
  62. package/server.json +7 -2
@@ -0,0 +1,94 @@
1
+ # Web API token providers
2
+
3
+ `FmsgClient` accepts either the existing `fmsgk_…` API-key string or a `TokenProvider`.
4
+ This is a client-library extension point. The MCP executable uses it for API keys and
5
+ optional [HTTP OAuth](oauth.md), including discovery, JWT validation and RFC 8693 exchange.
6
+
7
+ Import `TokenProvider`, `TokenProviderRequest` and `AccessToken` from
8
+ `@markmnl/fmsg-mcp/client` (also exported from the package root):
9
+
10
+ ```ts
11
+ interface TokenProvider {
12
+ getToken(request: {
13
+ apiUrl: string;
14
+ signal: AbortSignal;
15
+ forceRefresh: boolean;
16
+ }): Promise<{
17
+ accessToken: string;
18
+ address: string;
19
+ expiresAtMs: number;
20
+ }>;
21
+ close?(): void;
22
+ }
23
+ ```
24
+
25
+ Supply an implementation as `new FmsgClient(apiUrl, tokenProvider, options)`.
26
+ One provider and client belong to one caller, authorization grant and Web API URL.
27
+ Sharing a provider between clients risks mixing grants or closing another client's credentials.
28
+
29
+ ## Provider responsibilities
30
+
31
+ - Obtain a fresh **Web API** bearer access token whenever called. `apiUrl` is the
32
+ normalized, fixed upstream URL; never derive credential destinations from model input.
33
+ `forceRefresh: true` means the previous token was rejected with 401 or renewal was
34
+ explicitly requested. It must bypass any provider cache of that rejected token.
35
+ - Return the authenticated fmsg address that the token authorizes, preserving user-name
36
+ case, and a finite future expiry in milliseconds since the Unix epoch. Use trusted
37
+ authorization/exchange metadata; do not invent an expiry or infer identity from an
38
+ unverified incoming token. The generic client does not decode provider tokens.
39
+ - Honour `signal` for all network work. It aborts when renewal times out, the client
40
+ closes, or all callers waiting for that renewal cancel. Reject on failed/revoked
41
+ authorization; never fall back to an owner token, API key or another grant.
42
+ - Keep credentials out of errors and logs. `close()` synchronously releases retained
43
+ credentials/resources; it is called once when the owning client closes. Closing a
44
+ client is local cleanup, not remote OAuth-grant revocation.
45
+
46
+ ## Client responsibilities
47
+
48
+ The client caches immutable token snapshots and shares concurrent renewal. It renews
49
+ five minutes before expiry by default, capped at half the remaining lifetime when the
50
+ token is acquired, so a one-minute token is usable without constant re-exchange.
51
+ `refreshMarginMs: 0` disables early renewal, but never permits an expired cached token.
52
+ Each acquisition has the client's `timeoutMs` budget (60 seconds by default).
53
+
54
+ The first successful acquisition pins the address. A renewal for another address is
55
+ rejected before any request uses that token; changing identities requires a new client.
56
+ This is caller binding, not an additional messaging permission system. The Web API
57
+ still validates the actual credential, identity, scopes, visibility and host limits.
58
+
59
+ Protected HTTP requests retry once on 401 after renewal. Concurrent or late 401s for
60
+ the same old token reuse an already renewed token; 403 never triggers renewal.
61
+ Failed renewal discards the cache. Provider output must be a bearer token with a valid
62
+ address and expiry; an API key passed back as an access token is rejected.
63
+
64
+ `getToken(force?, signal?)` and `address(signal?)` support cancellation, as do existing
65
+ request methods that accept a signal. Cancelling one waiter leaves renewal available
66
+ to others; cancelling the last aborts acquisition. Late provider completion cannot
67
+ replace a later token. `close()` aborts outstanding token acquisition and HTTP work.
68
+
69
+ `openFmsgWebSocket(client, signal?)` obtains its bearer from the same provider/cache.
70
+ The optional signal cancels token acquisition before opening the socket. The caller
71
+ owns the returned WebSocket and must handle its events and close it; long-lived
72
+ connections do not gain automatic token renewal or revocation handling from this helper.
73
+ `wait_for_message` owns its socket and passes cancellation into acquisition. With
74
+ `reconnectOnTokenExpiry: true`, it closes and reopens sockets by the token metadata expiry,
75
+ retaining the original wait deadline and cursor; OAuth mode enables this option.
76
+
77
+ ## OAuth adapter contract
78
+
79
+ Validate the incoming MCP access token for the configured issuer and MCP audience,
80
+ then use authenticated [RFC 8693 token exchange](https://www.rfc-editor.org/rfc/rfc8693.html)
81
+ to obtain a separate upstream token. [MCP forbids passing the incoming token through
82
+ to the Web API](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerations).
83
+ Keep issuer, JWKS/discovery URLs, audiences, address-claim mapping and client credentials
84
+ configurable; no particular identity provider is required by this interface.
85
+
86
+ The IdP and Web API must agree on scopes and consented-identity binding before hosted
87
+ OAuth is enabled. Exchanged messaging tokens must not acquire owner key-management
88
+ rights or broaden identity via `X-FMSG-Act-As`. The Web API enforces those restrictions;
89
+ the tool list is not an authorization boundary. Browser consent and refresh/revocation endpoints belong to the authorization service and host. Offline JWT validation alone does not
90
+ provide immediate revocation of already issued upstream tokens.
91
+
92
+ Tests in `test/token-provider.test.ts` use registered opaque token fixtures to exercise
93
+ renewal, cancellation, caller isolation, and HTTP/WebSocket credential delivery. They
94
+ do not establish OAuth conformance, JWT verification, or compatibility with a real IdP.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@markmnl/fmsg-mcp",
3
3
  "mcpName": "io.github.markmnl/fmsg-mcp",
4
- "version": "0.1.3",
4
+ "version": "0.2.0",
5
5
  "description": "MCP server for fmsg: send and receive federated messages from any AI agent via a deployed fmsg Web API",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -42,6 +42,12 @@
42
42
  "dist/**/*.js",
43
43
  "dist/**/*.d.ts",
44
44
  "README.md",
45
+ "SECURITY.md",
46
+ "CHANGELOG.md",
47
+ "ROADMAP.md",
48
+ "docs/http-deployment.md",
49
+ "docs/token-providers.md",
50
+ "docs/oauth.md",
45
51
  "LICENSE",
46
52
  "server.json"
47
53
  ],
@@ -61,6 +67,7 @@
61
67
  },
62
68
  "dependencies": {
63
69
  "@modelcontextprotocol/server": "^2.0.0",
70
+ "jose": "^6.2.12",
64
71
  "ws": "^8.21.3",
65
72
  "zod": "^4.5.4"
66
73
  },
package/server.json CHANGED
@@ -6,13 +6,13 @@
6
6
  "url": "https://github.com/markmnl/fmsg-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.1.3",
9
+ "version": "0.2.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "registryBaseUrl": "https://registry.npmjs.org",
14
14
  "identifier": "@markmnl/fmsg-mcp",
15
- "version": "0.1.3",
15
+ "version": "0.2.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -32,6 +32,11 @@
32
32
  "name": "FMSG_DEFAULT_DOMAIN",
33
33
  "description": "Optional: lets short names resolve to @name@<domain>",
34
34
  "isRequired": false
35
+ },
36
+ {
37
+ "name": "FMSG_MCP_DOWNLOAD_DIR",
38
+ "description": "Optional local folder enabling streamed attachment saves over stdio",
39
+ "isRequired": false
35
40
  }
36
41
  ]
37
42
  }