@n24q02m/mcp-core 1.19.0-beta.3 → 1.19.0-beta.5
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 +228 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/relay/tool-helpers.d.ts +34 -6
- package/build/relay/tool-helpers.d.ts.map +1 -1
- package/build/relay/tool-helpers.js +49 -7
- package/build/relay/tool-helpers.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# mcp-core
|
|
2
|
+
|
|
3
|
+
**Shared foundation for building MCP servers -- Streamable HTTP transport, OAuth 2.1, browser-based credential setup, and a shared embedding daemon.**
|
|
4
|
+
|
|
5
|
+
<!-- BEGIN: AUTO-GENERATED-CROSS-PROMO -->
|
|
6
|
+
<details>
|
|
7
|
+
<summary><strong>Sister projects from n24q02m</strong> (click to expand)</summary>
|
|
8
|
+
|
|
9
|
+
| Project | Tagline | Tag |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| [better-code-review-graph](https://github.com/n24q02m/better-code-review-graph) | Knowledge graph for token-efficient code reviews -- semantic search and call-... | MCP |
|
|
12
|
+
| [better-email-mcp](https://github.com/n24q02m/better-email-mcp) | IMAP/SMTP email for AI agents -- read, send, organize folders, and manage att... | MCP |
|
|
13
|
+
| [better-godot-mcp](https://github.com/n24q02m/better-godot-mcp) | Composite MCP server for Godot Engine -- 17 composite tools for AI-assisted g... | MCP |
|
|
14
|
+
| [better-notion-mcp](https://github.com/n24q02m/better-notion-mcp) | Markdown-first Notion for AI agents -- pages, databases, blocks, and comments... | MCP |
|
|
15
|
+
| [better-telegram-mcp](https://github.com/n24q02m/better-telegram-mcp) | Telegram for AI agents -- messages, chats, media, and contacts across both bo... | MCP |
|
|
16
|
+
| [claude-plugins](https://github.com/n24q02m/claude-plugins) | Claude Code plugin marketplace for the n24q02m MCP servers -- install web sea... | Marketplace |
|
|
17
|
+
| [imagine-mcp](https://github.com/n24q02m/imagine-mcp) | Image and video understanding + generation for AI agents -- across Gemini, Op... | MCP |
|
|
18
|
+
| [jules-task-archiver](https://github.com/n24q02m/jules-task-archiver) | Chrome Extension for bulk operations on Jules tasks via batchexecute API -- a... | Tooling |
|
|
19
|
+
| [mcp-core](https://github.com/n24q02m/mcp-core) | Shared foundation for building MCP servers -- Streamable HTTP transport, OAut... | MCP |
|
|
20
|
+
| [mnemo-mcp](https://github.com/n24q02m/mnemo-mcp) | Persistent AI memory with hybrid search and embedded sync. Open, free, unlimi... | MCP |
|
|
21
|
+
| [qwen3-embed](https://github.com/n24q02m/qwen3-embed) | Lightweight Qwen3 text embedding and reranking via ONNX Runtime and GGUF | Library |
|
|
22
|
+
| [skret](https://github.com/n24q02m/skret) | Secrets without the server. | CLI |
|
|
23
|
+
| [tacet](https://github.com/n24q02m/tacet) | TACET: a self-distilling neuro-symbolic cascade that amortises LLM cost in kn... | Tooling |
|
|
24
|
+
| [web-core](https://github.com/n24q02m/web-core) | Shared web infrastructure package for search, scraping, HTTP security, and st... | Library |
|
|
25
|
+
| [wet-mcp](https://github.com/n24q02m/wet-mcp) | Open-source MCP server for AI agents: web search, content extraction, and lib... | MCP |
|
|
26
|
+
|
|
27
|
+
</details>
|
|
28
|
+
<!-- END: AUTO-GENERATED-CROSS-PROMO -->
|
|
29
|
+
|
|
30
|
+
## Table of contents
|
|
31
|
+
|
|
32
|
+
- [Packages](#packages)
|
|
33
|
+
- [What you get](#what-you-get)
|
|
34
|
+
- [Quick start (Python)](#quick-start-python)
|
|
35
|
+
- [Quick start (TypeScript)](#quick-start-typescript)
|
|
36
|
+
- [Documentation](#documentation)
|
|
37
|
+
- [Development](#development)
|
|
38
|
+
- [License](#license)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
mcp-core is the shared foundation for the n24q02m MCP servers: a Streamable
|
|
43
|
+
HTTP transport, an OAuth 2.1 Authorization Server, lifecycle management,
|
|
44
|
+
install automation, and a shared embedding daemon.
|
|
45
|
+
|
|
46
|
+
`mcp-core` is the **functional successor** to the archived
|
|
47
|
+
[`mcp-relay-core`](https://github.com/n24q02m/mcp-relay-core). All crypto,
|
|
48
|
+
storage, OAuth, relay, and schema modules from `mcp-relay-core` ship under
|
|
49
|
+
the same paths in `mcp-core` (1:1 superset), so downstream MCP servers can
|
|
50
|
+
migrate with a pure import + dependency rename. See the
|
|
51
|
+
[Migration guide](https://mcp.n24q02m.com/servers/mcp-core/migration/)
|
|
52
|
+
for the rename table.
|
|
53
|
+
|
|
54
|
+
## Packages
|
|
55
|
+
|
|
56
|
+
| Package | Language | Registry | Install |
|
|
57
|
+
|---------|----------|----------|---------|
|
|
58
|
+
| [`packages/core-py`](packages/core-py) | Python 3.13 | PyPI: [`n24q02m-mcp-core`](https://pypi.org/project/n24q02m-mcp-core/) | `pip install n24q02m-mcp-core` |
|
|
59
|
+
| [`packages/core-ts`](packages/core-ts) | TypeScript / Node 24 | npm: [`@n24q02m/mcp-core`](https://www.npmjs.com/package/@n24q02m/mcp-core) | `bun add @n24q02m/mcp-core` |
|
|
60
|
+
| [`packages/embedding-daemon`](packages/embedding-daemon) | Python 3.13 | PyPI: [`mcp-embedding-daemon`](https://pypi.org/project/mcp-embedding-daemon/) | `pip install mcp-embedding-daemon` |
|
|
61
|
+
|
|
62
|
+
All three packages share the same version (`semantic-release.toml` bumps both
|
|
63
|
+
Python `pyproject.toml` files plus the npm `package.json` in lockstep).
|
|
64
|
+
|
|
65
|
+
The Python core ships one optional extra for the LLM passthrough (litellm):
|
|
66
|
+
`pip install 'n24q02m-mcp-core[llm]'`.
|
|
67
|
+
|
|
68
|
+
## What you get
|
|
69
|
+
|
|
70
|
+
### `n24q02m-mcp-core` (Python) and `@n24q02m/mcp-core` (TypeScript)
|
|
71
|
+
|
|
72
|
+
These modules ship in **both** languages with a matching public API
|
|
73
|
+
(cross-language test vectors keep the crypto byte-for-byte identical):
|
|
74
|
+
|
|
75
|
+
- **`crypto/`** — ECDH P-256, AES-256-GCM, HKDF-SHA256 primitives.
|
|
76
|
+
Cross-language test vectors guarantee Python and TypeScript produce the
|
|
77
|
+
same ciphertext for the same input.
|
|
78
|
+
- **`storage/`** — `PerPluginStore`, the per-plugin encrypted credential
|
|
79
|
+
store. Single-user (stdio / HTTP) writes `~/.<plugin>-mcp/config.json`
|
|
80
|
+
encrypted with a machine-bound key; HTTP multi-user writes
|
|
81
|
+
`~/.<plugin>-mcp/subs/<sub>/config.json` encrypted with a key derived from
|
|
82
|
+
the `CREDENTIAL_SECRET` env var (salt `<plugin>:<sub>`). Pluggable
|
|
83
|
+
`CredentialBackend`s (`LocalFsBackend`, `CfKvBackend`) decouple the on-disk
|
|
84
|
+
layout from serverless deployments. Also ships session lock files and
|
|
85
|
+
config resolver helpers. The legacy shared `config.enc` file
|
|
86
|
+
(`storage.config_file`) is deprecated.
|
|
87
|
+
- **`auth/`** — the self-hosted OAuth 2.1 Authorization Server that downstream
|
|
88
|
+
servers actually run: `create_local_oauth_app` (Starlette ASGI app serving
|
|
89
|
+
`/authorize`, `/token`, the `/.well-known/oauth-*` metadata, and the
|
|
90
|
+
browser-rendered credential form), `render_credential_form`, the optional
|
|
91
|
+
shared-password gate at `/login` (`MCP_RELAY_PASSWORD`, empty disables it),
|
|
92
|
+
and `create_delegated_oauth_app` for upstream-redirect / device-code
|
|
93
|
+
multi-user flows.
|
|
94
|
+
- **`oauth/`** — OAuth 2.1 primitives consumed by `auth/`: `JWTIssuer`
|
|
95
|
+
(RS256), `SqliteUserStore` for multi-user mode, and `OAuthProvider`, the
|
|
96
|
+
legacy `mcp-relay-core` PKCE-over-relay provider retained for migration.
|
|
97
|
+
- **`relay/`** — `RelaySession`, `create_session`, `poll_for_result`,
|
|
98
|
+
`send_message` plus the EFF Diceware wordlist for passphrase generation.
|
|
99
|
+
This is the legacy `mcp-relay-core` ECDH relay-client path used by
|
|
100
|
+
`OAuthProvider`; the live setup UX is the `auth/` browser credential form.
|
|
101
|
+
- **`schema/`** — `RelayConfigSchema` TypedDict that downstream servers use
|
|
102
|
+
to declare their config form.
|
|
103
|
+
- **`transport/`** — `StreamableHTTPServer` wrapper around FastMCP /
|
|
104
|
+
`@modelcontextprotocol/sdk` Streamable HTTP transport, plus
|
|
105
|
+
`OAuthMiddleware` (RFC 6750 + RFC 9728 compliant Bearer validation).
|
|
106
|
+
- **`lifecycle/`** — `LifecycleLock` cross-platform file lock that prevents
|
|
107
|
+
two server instances from binding the same `(name, port)` pair.
|
|
108
|
+
|
|
109
|
+
### Python-only modules (`n24q02m-mcp-core`)
|
|
110
|
+
|
|
111
|
+
These have no TypeScript counterpart yet — they back the Python MCP servers
|
|
112
|
+
(wet, mnemo, code-review-graph, telegram, imagine):
|
|
113
|
+
|
|
114
|
+
- **`llm/`** — a thin passthrough over [litellm](https://github.com/BerriAI/litellm)
|
|
115
|
+
so every server talks to cloud providers the same way. Async + sync wrappers
|
|
116
|
+
for `completion`, `embedding`, `rerank`, `image_generation`,
|
|
117
|
+
`video_generation` / `video_status` / `video_content`; a graceful capability
|
|
118
|
+
check against the litellm registry (`check_capability`, `list_models`,
|
|
119
|
+
`suggest_models`, `supports_vision`); multi-key CSV rotation per provider
|
|
120
|
+
(`rotate_keys`, `split_keys`); and a direct Vertex AI **Express** adapter
|
|
121
|
+
(`completion_express`) that bypasses litellm where its `vertex_ai/` route
|
|
122
|
+
ignores the Express API key. Provider keys follow the litellm convention —
|
|
123
|
+
`GEMINI_API_KEY`, `OPENAI_API_KEY`, `XAI_API_KEY`, `ANTHROPIC_API_KEY`,
|
|
124
|
+
`COHERE_API_KEY`, `JINA_AI_API_KEY`, `GOOGLE_VERTEX_EXPRESS_API_KEY` (see
|
|
125
|
+
`llm.providers.PROVIDER_KEY_ENV`); any unlisted provider falls back to
|
|
126
|
+
`<PROVIDER>_API_KEY`. Requires the optional extra:
|
|
127
|
+
`pip install 'n24q02m-mcp-core[llm]'`.
|
|
128
|
+
- **`chains.py`** — the capability provider-chain primitive every server shares
|
|
129
|
+
(exported at the top level): `resolve_backend` makes the 3-way
|
|
130
|
+
cloud / local / unavailable decision, `run_with_fallback` walks an ordered
|
|
131
|
+
list of providers and returns the first non-empty result, and
|
|
132
|
+
`local_enabled_from_env` reads the per-capability `DISABLE_LOCAL_<X>` toggle
|
|
133
|
+
(`DISABLE_LOCAL_SEARCH` / `DISABLE_LOCAL_BROWSER` / `DISABLE_LOCAL_EMBED` /
|
|
134
|
+
`DISABLE_LOCAL_RERANK`).
|
|
135
|
+
- **`http/`** — SSRF-safe HTTP clients (`get_ssrf_safe_async_client`,
|
|
136
|
+
`get_ssrf_safe_sync_client`, `vet_api_base`) that block requests to private /
|
|
137
|
+
loopback / link-local addresses. Used to vet every user-supplied `api_base`
|
|
138
|
+
before it reaches an outbound provider call.
|
|
139
|
+
- **`install/`** — `AgentInstaller` writes MCP server entries into Claude Code,
|
|
140
|
+
Cursor, Codex, Windsurf, and OpenCode config files. Ships the
|
|
141
|
+
`mcp-clean-state` console script for wiping local config / session state.
|
|
142
|
+
|
|
143
|
+
### `mcp-embedding-daemon`
|
|
144
|
+
|
|
145
|
+
FastAPI HTTP server scaffold for the upcoming shared ONNX/GGUF embedding
|
|
146
|
+
backend. Currently exposes:
|
|
147
|
+
|
|
148
|
+
- `GET /health` — returns `{status, version}`
|
|
149
|
+
- `POST /embed` — returns 501 with a roadmap link (backend wiring lands in
|
|
150
|
+
the next release)
|
|
151
|
+
- `POST /rerank` — returns 501 with a roadmap link
|
|
152
|
+
|
|
153
|
+
CLI entry point: `mcp-embedding-daemon --host 127.0.0.1 --port 9800`.
|
|
154
|
+
|
|
155
|
+
## Quick start (Python)
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from mcp_core import RelaySession, create_session, decrypt
|
|
159
|
+
from mcp_core.transport.streamable_http import StreamableHTTPServer
|
|
160
|
+
from mcp_core.oauth import JWTIssuer
|
|
161
|
+
from mcp_core.transport.oauth_middleware import OAuthMiddleware
|
|
162
|
+
from fastmcp import FastMCP
|
|
163
|
+
|
|
164
|
+
mcp = FastMCP("my-server")
|
|
165
|
+
|
|
166
|
+
issuer = JWTIssuer("my-server")
|
|
167
|
+
issuer # Use issuer.issue_access_token(sub) / verify_access_token(token)
|
|
168
|
+
|
|
169
|
+
middleware = [OAuthMiddleware(issuer=issuer, resource_metadata_url="http://127.0.0.1:9876/.well-known/oauth-protected-resource")]
|
|
170
|
+
server = StreamableHTTPServer(mcp, port=9876, middleware=middleware)
|
|
171
|
+
server.run()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Quick start (TypeScript)
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
178
|
+
import { JWTIssuer } from '@n24q02m/mcp-core/oauth'
|
|
179
|
+
import { OAuthMiddleware, StreamableHTTPServer } from '@n24q02m/mcp-core/transport'
|
|
180
|
+
|
|
181
|
+
const server = new McpServer({ name: 'my-server', version: '0.0.0' })
|
|
182
|
+
const issuer = new JWTIssuer('my-server')
|
|
183
|
+
await issuer.init()
|
|
184
|
+
|
|
185
|
+
const middleware = new OAuthMiddleware({
|
|
186
|
+
jwtIssuer: issuer,
|
|
187
|
+
resourceMetadataUrl: 'http://127.0.0.1:9876/.well-known/oauth-protected-resource'
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const http = new StreamableHTTPServer({ server, port: 9876, oauthMiddleware: middleware })
|
|
191
|
+
await http.connect()
|
|
192
|
+
// Then mount http.handleRequest(req, res) on your http.Server / Express / Hono.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Documentation
|
|
196
|
+
|
|
197
|
+
Full docs at **[mcp.n24q02m.com/servers/mcp-core/architecture/](https://mcp.n24q02m.com/servers/mcp-core/architecture/)** (Foundation library section in the MCP n24q02m unified docs site):
|
|
198
|
+
|
|
199
|
+
- [Architecture](https://mcp.n24q02m.com/servers/mcp-core/architecture/) -- transport, OAuth AS, lifecycle, multi-user primitives
|
|
200
|
+
- [Trust model](https://mcp.n24q02m.com/servers/mcp-core/trust-model/) -- threat model + key-handling guarantees
|
|
201
|
+
- [Migration](https://mcp.n24q02m.com/servers/mcp-core/migration/) -- breaking-change history and upgrade paths from `mcp-relay-core`
|
|
202
|
+
- [Shared services](https://mcp.n24q02m.com/servers/mcp-core/shared-services/) -- embedding daemon + ancillary docker-compose stack
|
|
203
|
+
|
|
204
|
+
Source of truth lives in [`n24q02m/claude-plugins/plugins/mcp-core/`](https://github.com/n24q02m/claude-plugins/tree/main/plugins/mcp-core). Edit there; this repo's `docs/` directory is intentionally minimal post-migration.
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
mise run setup # install runtimes + deps + pre-commit hooks
|
|
210
|
+
bun install # root TypeScript workspace install
|
|
211
|
+
|
|
212
|
+
# Python (per package)
|
|
213
|
+
cd packages/core-py
|
|
214
|
+
uv sync --group dev
|
|
215
|
+
uv run pytest
|
|
216
|
+
uv run ty check
|
|
217
|
+
uv run ruff check .
|
|
218
|
+
|
|
219
|
+
# TypeScript
|
|
220
|
+
cd packages/core-ts
|
|
221
|
+
bun run test
|
|
222
|
+
bun run check
|
|
223
|
+
bun run build
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT
|
package/build/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { InMemoryAuthCache, type IOAuthSessionCache, OAuthProvider, type OAuthPr
|
|
|
6
6
|
export { type IUserCredentialStore, SqliteUserStore } from './oauth/user-store.js';
|
|
7
7
|
export { tryOpenBrowser } from './relay/browser.js';
|
|
8
8
|
export { createSession, generatePassphrase, notifyComplete, pollForResponses, pollForResult, type RelaySession, sendMessage } from './relay/client.js';
|
|
9
|
-
export { buildOpenRelayHandler, type OpenRelayHandlerOptions, type OpenRelayResult, registerOpenRelayTool, type ToolRegistrar } from './relay/tool-helpers.js';
|
|
9
|
+
export { buildOpenRelayHandler, type ElicitationServer, type OpenRelayHandlerOptions, type OpenRelayResult, registerOpenRelayTool, type ToolRegistrar } from './relay/tool-helpers.js';
|
|
10
10
|
export type * from './schema/types.js';
|
|
11
11
|
export { deleteConfig, exportConfig, importConfig, listConfigs, readConfig, scheduleReloadExit, writeConfig } from './storage/config-file.js';
|
|
12
12
|
export { clearMode, getMode, type ServerMode, setLocalMode } from './storage/mode.js';
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,2BAA2B,EAC3B,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,QAAQ,EACb,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAA;AACxB,cAAc,mBAAmB,CAAA;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,KAAK,YAAY,EACjB,WAAW,EACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAA;AAChC,mBAAmB,mBAAmB,CAAA;AACtC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,WAAW,EACZ,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrF,cAAc,uBAAuB,CAAA;AACrC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,WAAW,EAChB,gBAAgB,EACjB,MAAM,2BAA2B,CAAA;AAElC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,aAAa,EACd,MAAM,6BAA6B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,2BAA2B,EAC3B,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,QAAQ,EACb,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAA;AACxB,cAAc,mBAAmB,CAAA;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,KAAK,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,KAAK,YAAY,EACjB,WAAW,EACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAA;AAChC,mBAAmB,mBAAmB,CAAA;AACtC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,WAAW,EACZ,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrF,cAAc,uBAAuB,CAAA;AACrC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,WAAW,EAChB,gBAAgB,EACjB,MAAM,2BAA2B,CAAA;AAElC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,aAAa,EACd,MAAM,6BAA6B,CAAA"}
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,2EAA2E;AAC3E,OAAO,EACL,2BAA2B,EAI3B,uBAAuB,EACvB,mBAAmB,EAQnB,yBAAyB,EAGzB,oBAAoB,EAKrB,MAAM,iBAAiB,CAAA;AACxB,cAAc,mBAAmB,CAAA;AACjC,kDAAkD;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EACL,iBAAiB,EAEjB,aAAa,EAGd,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAA6B,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,aAAa,EAEb,WAAW,EACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,2EAA2E;AAC3E,OAAO,EACL,2BAA2B,EAI3B,uBAAuB,EACvB,mBAAmB,EAQnB,yBAAyB,EAGzB,oBAAoB,EAKrB,MAAM,iBAAiB,CAAA;AACxB,cAAc,mBAAmB,CAAA;AACjC,kDAAkD;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EACL,iBAAiB,EAEjB,aAAa,EAGd,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAA6B,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,aAAa,EAEb,WAAW,EACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,qBAAqB,EAIrB,qBAAqB,EAEtB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,WAAW,EACZ,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAmB,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrF,cAAc,uBAAuB,CAAA;AACrC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAElB,gBAAgB,EACjB,MAAM,2BAA2B,CAAA;AAClC,uEAAuE;AACvE,OAAO,EAGL,aAAa,EACd,MAAM,6BAA6B,CAAA"}
|
|
@@ -3,28 +3,52 @@
|
|
|
3
3
|
*
|
|
4
4
|
* After the stdio-pure + http-multi-user split, the relay config form is
|
|
5
5
|
* served by the HTTP server itself at ``<PUBLIC_URL>/authorize``. The
|
|
6
|
-
* ``config__open_relay`` tool
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* ``config__open_relay`` tool directs the user to that URL.
|
|
7
|
+
*
|
|
8
|
+
* When the connected client declares URL-mode elicitation
|
|
9
|
+
* (``capabilities.elicitation.url``, SEP-1036), the tool asks the client to
|
|
10
|
+
* present a consent prompt and open the URL via ``Server.elicitInput`` so the
|
|
11
|
+
* credential form is opened out-of-band without the LLM in the loop. Clients
|
|
12
|
+
* that do NOT declare the capability fall back to the legacy behaviour: the
|
|
13
|
+
* tool returns the URL and best-effort opens the user's default browser
|
|
14
|
+
* server-side. There is no daemon-bridge discovery, no respawn, and no
|
|
15
|
+
* session deduplication — the HTTP server's own session tracking handles
|
|
16
|
+
* concurrent setup attempts.
|
|
10
17
|
*
|
|
11
18
|
* In stdio mode, the relay form does not exist; the tool returns
|
|
12
19
|
* ``stdio_unsupported`` so plugin code can render a "switch to HTTP mode"
|
|
13
20
|
* message instead of misleading the user with an unreachable URL.
|
|
14
21
|
*/
|
|
22
|
+
import type { ClientCapabilities, ElicitRequestURLParams, ElicitResult } from '@modelcontextprotocol/sdk/types.js';
|
|
15
23
|
export interface OpenRelayResult {
|
|
16
24
|
url: string;
|
|
17
25
|
browserOpened: boolean;
|
|
18
26
|
status: 'configured' | 'unconfigured' | 'expired' | 'session_active' | 'stdio_unsupported';
|
|
27
|
+
elicitation?: 'accepted' | 'declined' | 'cancelled';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Minimal surface required to drive URL-mode elicitation. Satisfied by the
|
|
31
|
+
* low-level `Server` from `@modelcontextprotocol/sdk` (`mcp.server`), which
|
|
32
|
+
* exposes both `getClientCapabilities` and `elicitInput`.
|
|
33
|
+
*/
|
|
34
|
+
export interface ElicitationServer {
|
|
35
|
+
getClientCapabilities: () => ClientCapabilities | undefined;
|
|
36
|
+
elicitInput: (params: ElicitRequestURLParams) => Promise<ElicitResult>;
|
|
19
37
|
}
|
|
20
38
|
export interface OpenRelayHandlerOptions {
|
|
21
39
|
serverName: string;
|
|
22
40
|
publicUrl: string | null;
|
|
41
|
+
elicitation?: ElicitationServer;
|
|
23
42
|
}
|
|
24
43
|
/**
|
|
25
44
|
* Build the `open_relay` handler closure. Tests can call
|
|
26
45
|
* ``buildOpenRelayHandler({ serverName, publicUrl })`` directly without
|
|
27
46
|
* needing a real MCP server instance.
|
|
47
|
+
*
|
|
48
|
+
* When `elicitation` is supplied AND the client declared URL-mode
|
|
49
|
+
* elicitation, the handler drives `elicitInput` and returns the accept /
|
|
50
|
+
* decline / cancel outcome. Otherwise it returns the legacy dict
|
|
51
|
+
* (`{ url, browserOpened, status }`) unchanged.
|
|
28
52
|
*/
|
|
29
53
|
export declare function buildOpenRelayHandler(options: OpenRelayHandlerOptions): () => Promise<OpenRelayResult>;
|
|
30
54
|
/**
|
|
@@ -46,12 +70,16 @@ export interface ToolRegistrar {
|
|
|
46
70
|
*
|
|
47
71
|
* ```ts
|
|
48
72
|
* import { registerOpenRelayTool } from '@n24q02m/mcp-core'
|
|
49
|
-
* registerOpenRelayTool(mcp, SERVER_NAME, PUBLIC_URL)
|
|
73
|
+
* registerOpenRelayTool(mcp, SERVER_NAME, PUBLIC_URL, mcp.server)
|
|
50
74
|
* ```
|
|
51
75
|
*
|
|
52
76
|
* Pass ``null`` for ``publicUrl`` when the server is running in stdio mode;
|
|
53
77
|
* the tool will return ``status: 'stdio_unsupported'`` so the caller can
|
|
54
78
|
* surface a clear "switch to HTTP mode" message.
|
|
79
|
+
*
|
|
80
|
+
* Pass the low-level `Server` (`mcp.server`) as ``elicitation`` to enable
|
|
81
|
+
* URL-mode elicitation for capable clients. When omitted, the tool keeps
|
|
82
|
+
* the legacy browser-open behaviour for every client.
|
|
55
83
|
*/
|
|
56
|
-
export declare function registerOpenRelayTool(mcp: ToolRegistrar, serverName: string, publicUrl: string | null): void;
|
|
84
|
+
export declare function registerOpenRelayTool(mcp: ToolRegistrar, serverName: string, publicUrl: string | null, elicitation?: ElicitationServer): void;
|
|
57
85
|
//# sourceMappingURL=tool-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../../src/relay/tool-helpers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../../src/relay/tool-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAGlH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,OAAO,CAAA;IACtB,MAAM,EAAE,YAAY,GAAG,cAAc,GAAG,SAAS,GAAG,gBAAgB,GAAG,mBAAmB,CAAA;IAC1F,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,WAAW,CAAA;CACpD;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,qBAAqB,EAAE,MAAM,kBAAkB,GAAG,SAAS,CAAA;IAC3D,WAAW,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CACvE;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,CAAC,EAAE,iBAAiB,CAAA;CAChC;AAaD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,CAyBtG;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,CAAA;CACxG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,WAAW,CAAC,EAAE,iBAAiB,GAC9B,IAAI,CASN"}
|
|
@@ -3,20 +3,42 @@
|
|
|
3
3
|
*
|
|
4
4
|
* After the stdio-pure + http-multi-user split, the relay config form is
|
|
5
5
|
* served by the HTTP server itself at ``<PUBLIC_URL>/authorize``. The
|
|
6
|
-
* ``config__open_relay`` tool
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* ``config__open_relay`` tool directs the user to that URL.
|
|
7
|
+
*
|
|
8
|
+
* When the connected client declares URL-mode elicitation
|
|
9
|
+
* (``capabilities.elicitation.url``, SEP-1036), the tool asks the client to
|
|
10
|
+
* present a consent prompt and open the URL via ``Server.elicitInput`` so the
|
|
11
|
+
* credential form is opened out-of-band without the LLM in the loop. Clients
|
|
12
|
+
* that do NOT declare the capability fall back to the legacy behaviour: the
|
|
13
|
+
* tool returns the URL and best-effort opens the user's default browser
|
|
14
|
+
* server-side. There is no daemon-bridge discovery, no respawn, and no
|
|
15
|
+
* session deduplication — the HTTP server's own session tracking handles
|
|
16
|
+
* concurrent setup attempts.
|
|
10
17
|
*
|
|
11
18
|
* In stdio mode, the relay form does not exist; the tool returns
|
|
12
19
|
* ``stdio_unsupported`` so plugin code can render a "switch to HTTP mode"
|
|
13
20
|
* message instead of misleading the user with an unreachable URL.
|
|
14
21
|
*/
|
|
22
|
+
import { randomUUID } from 'node:crypto';
|
|
15
23
|
import { tryOpenBrowser } from './browser.js';
|
|
24
|
+
// Maps the client's ElicitResult.action to the tool's outcome field.
|
|
25
|
+
const ELICIT_ACTION_MAP = {
|
|
26
|
+
accept: 'accepted',
|
|
27
|
+
decline: 'declined',
|
|
28
|
+
cancel: 'cancelled'
|
|
29
|
+
};
|
|
30
|
+
function clientSupportsUrlElicitation(server) {
|
|
31
|
+
return server.getClientCapabilities()?.elicitation?.url != null;
|
|
32
|
+
}
|
|
16
33
|
/**
|
|
17
34
|
* Build the `open_relay` handler closure. Tests can call
|
|
18
35
|
* ``buildOpenRelayHandler({ serverName, publicUrl })`` directly without
|
|
19
36
|
* needing a real MCP server instance.
|
|
37
|
+
*
|
|
38
|
+
* When `elicitation` is supplied AND the client declared URL-mode
|
|
39
|
+
* elicitation, the handler drives `elicitInput` and returns the accept /
|
|
40
|
+
* decline / cancel outcome. Otherwise it returns the legacy dict
|
|
41
|
+
* (`{ url, browserOpened, status }`) unchanged.
|
|
20
42
|
*/
|
|
21
43
|
export function buildOpenRelayHandler(options) {
|
|
22
44
|
return async function openRelay() {
|
|
@@ -24,6 +46,22 @@ export function buildOpenRelayHandler(options) {
|
|
|
24
46
|
return { url: '', browserOpened: false, status: 'stdio_unsupported' };
|
|
25
47
|
}
|
|
26
48
|
const url = `${options.publicUrl.replace(/\/$/, '')}/authorize`;
|
|
49
|
+
const { elicitation } = options;
|
|
50
|
+
if (elicitation && clientSupportsUrlElicitation(elicitation)) {
|
|
51
|
+
const message = `Open the ${options.serverName} configuration page to enter your credentials securely in your browser.`;
|
|
52
|
+
const result = await elicitation.elicitInput({
|
|
53
|
+
mode: 'url',
|
|
54
|
+
message,
|
|
55
|
+
url,
|
|
56
|
+
elicitationId: randomUUID()
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
url,
|
|
60
|
+
browserOpened: false,
|
|
61
|
+
status: 'unconfigured',
|
|
62
|
+
elicitation: ELICIT_ACTION_MAP[result.action] ?? 'accepted'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
27
65
|
const opened = await tryOpenBrowser(url);
|
|
28
66
|
return { url, browserOpened: !!opened, status: 'unconfigured' };
|
|
29
67
|
};
|
|
@@ -36,15 +74,19 @@ export function buildOpenRelayHandler(options) {
|
|
|
36
74
|
*
|
|
37
75
|
* ```ts
|
|
38
76
|
* import { registerOpenRelayTool } from '@n24q02m/mcp-core'
|
|
39
|
-
* registerOpenRelayTool(mcp, SERVER_NAME, PUBLIC_URL)
|
|
77
|
+
* registerOpenRelayTool(mcp, SERVER_NAME, PUBLIC_URL, mcp.server)
|
|
40
78
|
* ```
|
|
41
79
|
*
|
|
42
80
|
* Pass ``null`` for ``publicUrl`` when the server is running in stdio mode;
|
|
43
81
|
* the tool will return ``status: 'stdio_unsupported'`` so the caller can
|
|
44
82
|
* surface a clear "switch to HTTP mode" message.
|
|
83
|
+
*
|
|
84
|
+
* Pass the low-level `Server` (`mcp.server`) as ``elicitation`` to enable
|
|
85
|
+
* URL-mode elicitation for capable clients. When omitted, the tool keeps
|
|
86
|
+
* the legacy browser-open behaviour for every client.
|
|
45
87
|
*/
|
|
46
|
-
export function registerOpenRelayTool(mcp, serverName, publicUrl) {
|
|
47
|
-
const handler = buildOpenRelayHandler({ serverName, publicUrl });
|
|
88
|
+
export function registerOpenRelayTool(mcp, serverName, publicUrl, elicitation) {
|
|
89
|
+
const handler = buildOpenRelayHandler({ serverName, publicUrl, elicitation });
|
|
48
90
|
mcp.tool({
|
|
49
91
|
name: 'config__open_relay',
|
|
50
92
|
description: `Open the relay configuration form for ${serverName} in the user's browser.`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-helpers.js","sourceRoot":"","sources":["../../src/relay/tool-helpers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tool-helpers.js","sourceRoot":"","sources":["../../src/relay/tool-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAyB7C,qEAAqE;AACrE,MAAM,iBAAiB,GAA0D;IAC/E,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,UAAU;IACnB,MAAM,EAAE,WAAW;CACpB,CAAA;AAED,SAAS,4BAA4B,CAAC,MAAyB;IAC7D,OAAO,MAAM,CAAC,qBAAqB,EAAE,EAAE,WAAW,EAAE,GAAG,IAAI,IAAI,CAAA;AACjE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgC;IACpE,OAAO,KAAK,UAAU,SAAS;QAC7B,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YACvB,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAA;QACvE,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAA;QAC/D,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAA;QAC/B,IAAI,WAAW,IAAI,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7D,MAAM,OAAO,GAAG,YAAY,OAAO,CAAC,UAAU,yEAAyE,CAAA;YACvH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC;gBAC3C,IAAI,EAAE,KAAK;gBACX,OAAO;gBACP,GAAG;gBACH,aAAa,EAAE,UAAU,EAAE;aAC5B,CAAC,CAAA;YACF,OAAO;gBACL,GAAG;gBACH,aAAa,EAAE,KAAK;gBACpB,MAAM,EAAE,cAAc;gBACtB,WAAW,EAAE,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,UAAU;aAC5D,CAAA;QACH,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAA;QACxC,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;IACjE,CAAC,CAAA;AACH,CAAC;AAWD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAkB,EAClB,UAAkB,EAClB,SAAwB,EACxB,WAA+B;IAE/B,MAAM,OAAO,GAAG,qBAAqB,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAA;IAC7E,GAAG,CAAC,IAAI,CACN;QACE,IAAI,EAAE,oBAAoB;QAC1B,WAAW,EAAE,yCAAyC,UAAU,yBAAyB;KAC1F,EACD,OAAO,CACR,CAAA;AACH,CAAC"}
|