@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.
- package/CHANGELOG.md +49 -0
- package/README.md +78 -22
- package/ROADMAP.md +52 -0
- package/SECURITY.md +72 -0
- package/dist/auth.d.ts +14 -6
- package/dist/auth.js +115 -49
- package/dist/client/client.d.ts +34 -24
- package/dist/client/client.js +152 -105
- package/dist/client/errors.d.ts +19 -0
- package/dist/client/errors.js +51 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/message-id.d.ts +1 -0
- package/dist/client/message-id.js +7 -0
- package/dist/client/stream.d.ts +12 -0
- package/dist/client/stream.js +83 -0
- package/dist/client/token-provider.d.ts +27 -0
- package/dist/client/token-provider.js +54 -0
- package/dist/client/types.d.ts +8 -4
- package/dist/client/url.d.ts +4 -0
- package/dist/client/url.js +35 -0
- package/dist/client/ws.d.ts +2 -1
- package/dist/client/ws.js +9 -3
- package/dist/config.d.ts +7 -3
- package/dist/config.js +15 -6
- package/dist/context.d.ts +6 -3
- package/dist/context.js +9 -2
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +18 -26
- package/dist/http.d.ts +4 -3
- package/dist/http.js +158 -23
- package/dist/index.js +27 -11
- package/dist/instructions.js +10 -5
- package/dist/oauth/config.d.ts +11 -0
- package/dist/oauth/config.js +46 -0
- package/dist/oauth/errors.d.ts +18 -0
- package/dist/oauth/errors.js +40 -0
- package/dist/oauth/issuer.d.ts +31 -0
- package/dist/oauth/issuer.js +185 -0
- package/dist/oauth/provider.d.ts +23 -0
- package/dist/oauth/provider.js +95 -0
- package/dist/oauth/scopes.d.ts +6 -0
- package/dist/oauth/scopes.js +31 -0
- package/dist/render.d.ts +6 -1
- package/dist/render.js +27 -11
- package/dist/resources.js +18 -9
- package/dist/server.js +2 -0
- package/dist/thread.js +15 -14
- package/dist/tools/common.js +8 -3
- package/dist/tools/identity.js +1 -1
- package/dist/tools/list.js +3 -3
- package/dist/tools/read.js +26 -43
- package/dist/tools/save.d.ts +2 -0
- package/dist/tools/save.js +84 -0
- package/dist/tools/send.js +9 -13
- package/dist/tools/wait.js +14 -5
- package/dist/wait.d.ts +15 -1
- package/dist/wait.js +232 -84
- package/docs/http-deployment.md +53 -0
- package/docs/oauth.md +163 -0
- package/docs/token-providers.md +94 -0
- package/package.json +8 -1
- 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.
|
|
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.
|
|
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.
|
|
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
|
}
|