@novorb/mcp-core 2.1.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -163
- package/bin/mcp-core-jest.mjs +112 -0
- package/dist/auth.d.ts +57 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +74 -0
- package/dist/auth.js.map +1 -0
- package/dist/bootstrap.d.ts +129 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +206 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/client-input.d.ts +72 -0
- package/dist/client-input.d.ts.map +1 -0
- package/dist/client-input.js +109 -0
- package/dist/client-input.js.map +1 -0
- package/dist/credential-checks.d.ts +80 -0
- package/dist/credential-checks.d.ts.map +1 -0
- package/dist/credential-checks.js +189 -0
- package/dist/credential-checks.js.map +1 -0
- package/dist/credential-health.d.ts +12 -0
- package/dist/credential-health.d.ts.map +1 -0
- package/dist/credential-health.js +12 -0
- package/dist/credential-health.js.map +1 -0
- package/dist/credential-report.d.ts +46 -0
- package/dist/credential-report.d.ts.map +1 -0
- package/dist/credential-report.js +135 -0
- package/dist/credential-report.js.map +1 -0
- package/dist/credential-status-tool.d.ts +20 -0
- package/dist/credential-status-tool.d.ts.map +1 -0
- package/dist/credential-status-tool.js +33 -0
- package/dist/credential-status-tool.js.map +1 -0
- package/dist/credentials.d.ts +288 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +652 -0
- package/dist/credentials.js.map +1 -0
- package/dist/express-peers.d.ts +29 -0
- package/dist/express-peers.d.ts.map +1 -0
- package/dist/express-peers.js +36 -0
- package/dist/express-peers.js.map +1 -0
- package/dist/express.d.ts +74 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +164 -0
- package/dist/express.js.map +1 -0
- package/dist/http-client.d.ts +277 -42
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +620 -180
- package/dist/http-client.js.map +1 -1
- package/dist/index.d.ts +27 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -8
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +28 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +37 -0
- package/dist/limits.js.map +1 -0
- package/dist/list-pagination.d.ts +41 -0
- package/dist/list-pagination.d.ts.map +1 -0
- package/dist/list-pagination.js +104 -0
- package/dist/list-pagination.js.map +1 -0
- package/dist/net.d.ts +112 -0
- package/dist/net.d.ts.map +1 -0
- package/dist/net.js +490 -0
- package/dist/net.js.map +1 -0
- package/dist/notifications.d.ts +78 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +173 -0
- package/dist/notifications.js.map +1 -0
- package/dist/pagination.d.ts +236 -5
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +393 -6
- package/dist/pagination.js.map +1 -1
- package/dist/prompts.d.ts +70 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +109 -0
- package/dist/prompts.js.map +1 -0
- package/dist/protocol-errors.d.ts +15 -0
- package/dist/protocol-errors.d.ts.map +1 -0
- package/dist/protocol-errors.js +28 -0
- package/dist/protocol-errors.js.map +1 -0
- package/dist/protocol-logging.d.ts +37 -0
- package/dist/protocol-logging.d.ts.map +1 -0
- package/dist/protocol-logging.js +77 -0
- package/dist/protocol-logging.js.map +1 -0
- package/dist/rate-limiter.d.ts +36 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +79 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/request-state.d.ts +74 -0
- package/dist/request-state.d.ts.map +1 -0
- package/dist/request-state.js +190 -0
- package/dist/request-state.js.map +1 -0
- package/dist/resources.d.ts +181 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +302 -0
- package/dist/resources.js.map +1 -0
- package/dist/result-limits.d.ts +80 -0
- package/dist/result-limits.d.ts.map +1 -0
- package/dist/result-limits.js +248 -0
- package/dist/result-limits.js.map +1 -0
- package/dist/retry.d.ts +79 -23
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +170 -57
- package/dist/retry.js.map +1 -1
- package/dist/sdk/client-stdio.d.ts +6 -0
- package/dist/sdk/client-stdio.d.ts.map +1 -0
- package/dist/sdk/client-stdio.js +6 -0
- package/dist/sdk/client-stdio.js.map +1 -0
- package/dist/sdk/client.d.ts +6 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +6 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/express.d.ts +3 -0
- package/dist/sdk/express.d.ts.map +1 -0
- package/dist/sdk/express.js +11 -0
- package/dist/sdk/express.js.map +1 -0
- package/dist/sdk/node.d.ts +6 -0
- package/dist/sdk/node.d.ts.map +1 -0
- package/dist/sdk/node.js +6 -0
- package/dist/sdk/node.js.map +1 -0
- package/dist/sdk/server-stdio.d.ts +6 -0
- package/dist/sdk/server-stdio.d.ts.map +1 -0
- package/dist/sdk/server-stdio.js +6 -0
- package/dist/sdk/server-stdio.js.map +1 -0
- package/dist/sdk/server.d.ts +6 -0
- package/dist/sdk/server.d.ts.map +1 -0
- package/dist/sdk/server.js +6 -0
- package/dist/sdk/server.js.map +1 -0
- package/dist/server.d.ts +88 -35
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +98 -44
- package/dist/server.js.map +1 -1
- package/dist/task-request.d.ts +20 -0
- package/dist/task-request.d.ts.map +1 -0
- package/dist/task-request.js +43 -0
- package/dist/task-request.js.map +1 -0
- package/dist/task-store.d.ts +230 -0
- package/dist/task-store.d.ts.map +1 -0
- package/dist/task-store.js +459 -0
- package/dist/task-store.js.map +1 -0
- package/dist/task-tools.d.ts +115 -0
- package/dist/task-tools.d.ts.map +1 -0
- package/dist/task-tools.js +397 -0
- package/dist/task-tools.js.map +1 -0
- package/dist/testing/client.d.ts +25 -0
- package/dist/testing/client.d.ts.map +1 -0
- package/dist/testing/client.js +11 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/in-memory.d.ts +26 -0
- package/dist/testing/in-memory.d.ts.map +1 -0
- package/dist/testing/in-memory.js +41 -0
- package/dist/testing/in-memory.js.map +1 -0
- package/dist/testing/index.d.ts +20 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +20 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/isolated-dirs.d.ts +27 -0
- package/dist/testing/isolated-dirs.d.ts.map +1 -0
- package/dist/testing/isolated-dirs.js +67 -0
- package/dist/testing/isolated-dirs.js.map +1 -0
- package/dist/testing/logs.d.ts +61 -0
- package/dist/testing/logs.d.ts.map +1 -0
- package/dist/testing/logs.js +113 -0
- package/dist/testing/logs.js.map +1 -0
- package/dist/testing/raw-http.d.ts +25 -0
- package/dist/testing/raw-http.d.ts.map +1 -0
- package/dist/testing/raw-http.js +41 -0
- package/dist/testing/raw-http.js.map +1 -0
- package/dist/testing/raw-peer.d.ts +43 -0
- package/dist/testing/raw-peer.d.ts.map +1 -0
- package/dist/testing/raw-peer.js +101 -0
- package/dist/testing/raw-peer.js.map +1 -0
- package/dist/testing/recording-server.d.ts +38 -0
- package/dist/testing/recording-server.d.ts.map +1 -0
- package/dist/testing/recording-server.js +38 -0
- package/dist/testing/recording-server.js.map +1 -0
- package/dist/testing/server-process.d.ts +76 -0
- package/dist/testing/server-process.d.ts.map +1 -0
- package/dist/testing/server-process.js +241 -0
- package/dist/testing/server-process.js.map +1 -0
- package/dist/testing/tools.d.ts +53 -0
- package/dist/testing/tools.d.ts.map +1 -0
- package/dist/testing/tools.js +98 -0
- package/dist/testing/tools.js.map +1 -0
- package/dist/testing/unit.d.ts +52 -0
- package/dist/testing/unit.d.ts.map +1 -0
- package/dist/testing/unit.js +95 -0
- package/dist/testing/unit.js.map +1 -0
- package/dist/tools.d.ts +205 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +370 -0
- package/dist/tools.js.map +1 -0
- package/dist/transport.d.ts +143 -52
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +292 -123
- package/dist/transport.js.map +1 -1
- package/dist/url-path.d.ts +49 -0
- package/dist/url-path.d.ts.map +1 -0
- package/dist/url-path.js +165 -0
- package/dist/url-path.js.map +1 -0
- package/dist/utils/dataStore.d.ts +143 -0
- package/dist/utils/dataStore.d.ts.map +1 -0
- package/dist/utils/dataStore.js +429 -0
- package/dist/utils/dataStore.js.map +1 -0
- package/dist/utils/envLoader.d.ts +129 -0
- package/dist/utils/envLoader.d.ts.map +1 -0
- package/dist/utils/envLoader.js +399 -0
- package/dist/utils/envLoader.js.map +1 -0
- package/dist/utils/logger.d.ts +181 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +664 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/redact.d.ts +50 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +318 -0
- package/dist/utils/redact.js.map +1 -0
- package/docs/api-reference.md +29 -0
- package/docs/building-a-server.md +63 -0
- package/docs/credentials.md +131 -0
- package/docs/data-store.md +37 -0
- package/docs/env.md +38 -0
- package/docs/features.md +28 -0
- package/docs/http-client.md +116 -0
- package/docs/logger.md +100 -0
- package/docs/migrating-to-v3.md +45 -0
- package/docs/outbound-urls.md +104 -0
- package/docs/pagination.md +45 -0
- package/docs/protocol-features.md +62 -0
- package/docs/resources-prompts-tasks.md +95 -0
- package/docs/sdk.md +33 -0
- package/docs/server.md +68 -0
- package/docs/testing-harness.md +139 -0
- package/docs/testing.md +151 -0
- package/docs/tools.md +88 -0
- package/docs/transport.md +85 -0
- package/examples/testing/tools.integration.test.example.ts +312 -0
- package/examples/testing/tools.unit.test.example.ts +149 -0
- package/package.json +130 -17
- package/dist/logger.d.ts +0 -80
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -237
- package/dist/logger.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,202 +4,116 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/Novorb/mcp-core/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@novorb/mcp-core)
|
|
7
|
-
[](https://nodejs.org/)
|
|
8
|
+
[]()
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
|
|
11
11
|
#### Quality Assurance
|
|
12
12
|
|
|
13
|
-
[]()
|
|
14
|
+
[](docs/sdk.md#known-advisories)
|
|
15
15
|
[-brightgreen)](https://fallow.tools)
|
|
16
16
|
[](https://fallow.tools)
|
|
17
17
|
|
|
18
|
-
Shared TypeScript
|
|
18
|
+
Shared TypeScript foundation for building MCP (Model Context Protocol) servers: bootstrap, transport, tools, logging, HTTP clients, credentials and testing, so every server gets the same tested behaviour without reimplementing it. Version 3 is built on the MCP SDK v2 packages.
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
22
|
+
- **Bootstrap** (`runMcpServer`): one entry point for every server, from env loading to clean shutdown
|
|
23
|
+
- **Transport**: stdio and stateless streamable HTTP with bearer auth, plus an adapter for existing Express apps
|
|
24
|
+
- **Tools** (`registerTools`): central logging, safe error mapping, result caps and tamper-checked cursors
|
|
25
|
+
- **Protocol features**: logging, elicitation, sampling, roots, progress and cancellation across both protocol eras
|
|
26
|
+
- **Resources, prompts and notifications**: registration helpers, runtime toggles and change events on every transport
|
|
27
|
+
- **Tasks and requestState**: long-running tools with file or in-memory stores, and sealed multi-step state
|
|
28
|
+
- **HTTP client, retry and pagination**: one hardened client per upstream, one retry policy, page walkers for common API shapes
|
|
29
|
+
- **Guarded outbound URLs**: safe fetches of caller-supplied URLs and safe encoding of path segments
|
|
30
|
+
- **Credential health**: startup checks, key pools with rotation, and a report on every result
|
|
31
|
+
- **Env and data store**: one env loader with typed validation, and traversal-safe, atomic disk writes
|
|
32
|
+
- **Logger**: console and rotating file sinks with secret redaction and PII masking
|
|
33
|
+
- **Testing harness**: unit and integration helpers for your own server, and a cross-platform jest runner
|
|
34
|
+
- **The SDK through mcp-core**: a curated entry plus full re-exports, so servers depend on one package
|
|
29
35
|
|
|
30
36
|
## Documentation
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
- **[docs/TESTING.md](docs/TESTING.md)** — test runner commands, conventions, and guidelines
|
|
38
|
+
**Getting started**
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
- [Building a server](docs/building-a-server.md): a minimal server and what `runMcpServer` does
|
|
41
|
+
- [Features](docs/features.md): the full feature list in detail
|
|
42
|
+
- [API reference](docs/api-reference.md): every import path and what it exports
|
|
43
|
+
- [The MCP SDK](docs/sdk.md): importing the SDK through mcp-core, and what SDK v2 changes
|
|
44
|
+
- [Migrating to 3.0](docs/migrating-to-v3.md): breaking changes and moving a low-level `Server` consumer
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
pnpm add @novorb/mcp-core
|
|
39
|
-
```
|
|
46
|
+
**Serving**
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
- [Server](docs/server.md): the server builder, annotation presets, list pagination
|
|
49
|
+
- [Transport and Express adapter](docs/transport.md): stdio, HTTP, auth and guards
|
|
50
|
+
- [Tools](docs/tools.md): definitions, response limits, cursors
|
|
51
|
+
- [Protocol features](docs/protocol-features.md): logging, elicitation, sampling, roots, progress, cancellation
|
|
52
|
+
- [Resources, prompts, notifications, tasks, requestState](docs/resources-prompts-tasks.md)
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
**Upstreams and configuration**
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
- [HTTP client and retry](docs/http-client.md)
|
|
57
|
+
- [Pagination](docs/pagination.md)
|
|
58
|
+
- [Outbound URLs](docs/outbound-urls.md): `guardedFetch` and URL path segments
|
|
59
|
+
- [Env](docs/env.md)
|
|
60
|
+
- [Credential health](docs/credentials.md)
|
|
61
|
+
- [Data store](docs/data-store.md)
|
|
62
|
+
- [Logger](docs/logger.md)
|
|
46
63
|
|
|
47
|
-
|
|
48
|
-
import { initLogger, LogLevel } from '@novorb/mcp-core';
|
|
64
|
+
**Testing and contributing**
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
logDir: './logs',
|
|
54
|
-
level: LogLevel.INFO,
|
|
55
|
-
});
|
|
66
|
+
- [Testing your server](docs/testing-harness.md): the `./testing` harness and `mcp-core-jest`
|
|
67
|
+
- [Testing mcp-core](docs/testing.md): this repo's test commands and conventions
|
|
68
|
+
- [CLAUDE.md](CLAUDE.md): agent and contributor context
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
const logger = initLogger({
|
|
59
|
-
output: 'stderr',
|
|
60
|
-
format: 'json',
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
logger.info('Server started', 'Server');
|
|
64
|
-
logger.error('Request failed', 'HttpClient', { status: 500, path: '/api/items' });
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### HTTP Client
|
|
68
|
-
|
|
69
|
-
Pre-configured axios wrapper with automatic retry on transient failures (429, 5xx, network errors) for idempotent methods, request/response logging, and pagination support via `X-Total-Count` headers.
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import { createHttpClient } from '@novorb/mcp-core';
|
|
73
|
-
|
|
74
|
-
const client = createHttpClient({
|
|
75
|
-
baseURL: 'https://api.example.com',
|
|
76
|
-
headers: { 'Authorization': 'Bearer token' },
|
|
77
|
-
retries: 3,
|
|
78
|
-
logContext: 'MyApiClient',
|
|
79
|
-
});
|
|
70
|
+
## Quick Start
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
await client.post('/items', { name: 'new item' });
|
|
72
|
+
```bash
|
|
73
|
+
pnpm add @novorb/mcp-core zod
|
|
84
74
|
```
|
|
85
75
|
|
|
86
|
-
### Retry
|
|
87
|
-
|
|
88
|
-
Exponential backoff with jitter for retrying failed operations. Used internally by the HTTP client, but also available standalone.
|
|
89
|
-
|
|
90
76
|
```typescript
|
|
91
|
-
import {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
77
|
+
import { z } from 'zod';
|
|
78
|
+
import { runMcpServer } from '@novorb/mcp-core/bootstrap';
|
|
79
|
+
import { packageRootFrom } from '@novorb/mcp-core/logger';
|
|
80
|
+
import { TOOL_ANNOTATIONS } from '@novorb/mcp-core/server';
|
|
81
|
+
import { defineTool, registerTools, toolResult } from '@novorb/mcp-core/tools';
|
|
82
|
+
|
|
83
|
+
await runMcpServer({
|
|
84
|
+
rootDir: packageRootFrom(import.meta.url),
|
|
85
|
+
env: [],
|
|
86
|
+
create: (_shared, _req, { env, info, createServer }) => {
|
|
87
|
+
const server = createServer();
|
|
88
|
+
registerTools(
|
|
89
|
+
server,
|
|
90
|
+
[
|
|
91
|
+
defineTool({
|
|
92
|
+
name: 'hello',
|
|
93
|
+
description: 'Say hello',
|
|
94
|
+
inputSchema: z.object({ name: z.string() }),
|
|
95
|
+
annotations: TOOL_ANNOTATIONS.readOnly,
|
|
96
|
+
handler: async ({ name }) => toolResult(`Hello ${name}`),
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
{ env, serverName: info.name },
|
|
100
|
+
);
|
|
101
|
+
return server;
|
|
102
|
+
},
|
|
100
103
|
});
|
|
101
104
|
```
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Normalize pagination parameters, compute metadata, and paginate arrays in memory.
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
import { normalizePagination, createPaginationMetadata, paginateArray } from '@novorb/mcp-core';
|
|
109
|
-
|
|
110
|
-
const params = normalizePagination({ page: 3, per_page: 200 });
|
|
111
|
-
// { page: 3, per_page: 100 } (capped at max)
|
|
112
|
-
|
|
113
|
-
const metadata = createPaginationMetadata(500, params);
|
|
114
|
-
// { totalCount: 500, page: 3, perPage: 100, totalPages: 5 }
|
|
115
|
-
|
|
116
|
-
const { data, metadata } = paginateArray(allItems, { page: 2, per_page: 20 });
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Transport
|
|
120
|
-
|
|
121
|
-
Unified transport that selects between stdio and stateless streamable HTTP based on the `MCP_TRANSPORT` environment variable. HTTP mode is fail-closed (refuses to start without auth unless explicitly disabled) and supports Bearer-token auth, body-size caps, and opt-in DNS-rebinding protection.
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
import { startMcpServer } from '@novorb/mcp-core/transport';
|
|
125
|
-
|
|
126
|
-
// Factory (recommended for HTTP — fresh server instance per request):
|
|
127
|
-
await startMcpServer(() => createServer(services));
|
|
128
|
-
|
|
129
|
-
// Server instance (fine for stdio):
|
|
130
|
-
await startMcpServer(server);
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
| Variable | Default | Description |
|
|
134
|
-
|----------|---------|-------------|
|
|
135
|
-
| `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` |
|
|
136
|
-
| `PORT` | `8080` | HTTP listen port |
|
|
137
|
-
| `MCP_HTTP_HOST` | `0.0.0.0` | Bind address (`127.0.0.1` for local-only) |
|
|
138
|
-
| `MCP_API_KEY` | none | Bearer token for HTTP auth (required in HTTP mode unless disabled) |
|
|
139
|
-
| `MCP_AUTH_DISABLED` | `false` | Set `true` to run HTTP without auth (only behind a fronting gateway) |
|
|
140
|
-
| `MCP_MAX_BODY_BYTES` | 4 MiB | Max accepted POST body size |
|
|
141
|
-
| `MCP_ALLOWED_HOSTS` | none | Comma-separated Host allowlist (enables DNS-rebinding protection) |
|
|
142
|
-
| `MCP_ALLOWED_ORIGINS` | none | Comma-separated Origin allowlist (enables DNS-rebinding protection) |
|
|
143
|
-
|
|
144
|
-
### Server
|
|
145
|
-
|
|
146
|
-
Import MCP server-building primitives from here instead of the SDK directly, to centralise the MCP dependency.
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
import { createMcpServer, TOOL_ANNOTATIONS, completable } from '@novorb/mcp-core/server';
|
|
150
|
-
import type { McpToolAnnotations, McpCallToolResult } from '@novorb/mcp-core/server';
|
|
151
|
-
|
|
152
|
-
const server = createMcpServer('my-mcp', '1.0.0');
|
|
153
|
-
|
|
154
|
-
server.registerTool('lookup', {
|
|
155
|
-
description: 'Look up a value',
|
|
156
|
-
inputSchema: { key: z.string() },
|
|
157
|
-
outputSchema: { value: z.string(), found: z.boolean() },
|
|
158
|
-
annotations: TOOL_ANNOTATIONS.readOnly,
|
|
159
|
-
}, async ({ key }) => ({
|
|
160
|
-
content: [{ type: 'text', text: value }],
|
|
161
|
-
structuredContent: { value, found: true },
|
|
162
|
-
}));
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**`TOOL_ANNOTATIONS` presets:** `readOnly` (lookups/searches), `idempotentWrite` (upserts), `mutableWrite` (non-idempotent writes), `destructive` (deletes/overwrites).
|
|
166
|
-
|
|
167
|
-
## Testing
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
pnpm test # 122 tests
|
|
171
|
-
pnpm run test:watch # watch mode
|
|
172
|
-
pnpm run test:coverage
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
> **Windows note:** the test scripts set `NODE_OPTIONS=` inline, which is Unix shell syntax. On Windows, invoke jest directly via a bash shell. See [docs/TESTING.md](docs/TESTING.md).
|
|
176
|
-
|
|
177
|
-
## Building
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
pnpm run build # tsc → dist/
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
This is an ESM package (`"type": "module"`). `tsconfig.json` is self-contained so it builds standalone.
|
|
184
|
-
|
|
185
|
-
## CI / Releasing
|
|
186
|
-
|
|
187
|
-
GitHub Actions (`.github/workflows/`):
|
|
188
|
-
|
|
189
|
-
- **CI** (`ci.yml`) — runs build + test on every push to `main` and on pull requests.
|
|
190
|
-
- **Publish** (`publish.yml`) — on a pushed version tag `v*`, runs build + test, verifies the
|
|
191
|
-
tag matches `package.json` `version`, then publishes to the public npm registry (with provenance).
|
|
106
|
+
The server runs on stdio by default; set `MCP_TRANSPORT=http` for HTTP. Continue with [Building a server](docs/building-a-server.md).
|
|
192
107
|
|
|
193
|
-
|
|
108
|
+
## Development
|
|
194
109
|
|
|
195
110
|
```bash
|
|
196
|
-
|
|
197
|
-
#
|
|
198
|
-
|
|
111
|
+
pnpm run build # tsc to dist/
|
|
112
|
+
pnpm test # all suites
|
|
113
|
+
pnpm run qa:all # full QA matrix
|
|
199
114
|
```
|
|
200
115
|
|
|
201
|
-
|
|
202
|
-
the `@novorb` scope) under *Settings → Secrets and variables → Actions*.
|
|
116
|
+
This is an ESM package (`"type": "module"`). See [Testing mcp-core](docs/testing.md).
|
|
203
117
|
|
|
204
118
|
## License
|
|
205
119
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mcp-core-jest: a cross-platform jest launcher for ESM MCP servers (and for mcp-core itself).
|
|
4
|
+
*
|
|
5
|
+
* ESM jest suites (`"type": "module"` + ts-jest useESM) need the node binary started with
|
|
6
|
+
* `--experimental-vm-modules`. The usual `NODE_OPTIONS=--experimental-vm-modules jest` script is POSIX
|
|
7
|
+
* inline-env syntax and does not run under PowerShell or cmd. This launcher starts node with the flag
|
|
8
|
+
* and runs the CALLER's jest: jest is resolved from the current working directory (the consumer's
|
|
9
|
+
* package), so it stays the consumer's devDependency and mcp-core never depends on it at runtime.
|
|
10
|
+
*
|
|
11
|
+
* "test:unit": "mcp-core-jest tests/unit",
|
|
12
|
+
* "test:integration": "mcp-core-jest tests/integration --runInBand --coverage=false"
|
|
13
|
+
*
|
|
14
|
+
* Every argument is passed through to jest unchanged. The exit code is jest's; a signal that ends jest
|
|
15
|
+
* ends this process with the same signal; SIGTERM/SIGHUP are forwarded on POSIX and SIGINT (which reaches jest
|
|
16
|
+
* from the terminal) is waited out, never doubled.
|
|
17
|
+
*/
|
|
18
|
+
import { spawn } from 'node:child_process';
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
import { dirname, join, resolve } from 'node:path';
|
|
22
|
+
|
|
23
|
+
/** The directory of the `jest` package that owns `entry` (the nearest package.json named jest). */
|
|
24
|
+
function jestPackageRoot(entry) {
|
|
25
|
+
let dir = dirname(entry);
|
|
26
|
+
for (;;) {
|
|
27
|
+
const manifest = join(dir, 'package.json');
|
|
28
|
+
if (existsSync(manifest)) {
|
|
29
|
+
try {
|
|
30
|
+
if (JSON.parse(readFileSync(manifest, 'utf8')).name === 'jest') return dir;
|
|
31
|
+
} catch {
|
|
32
|
+
/* keep walking */
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const parent = resolve(dir, '..');
|
|
36
|
+
if (parent === dir) return null;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `jest/bin/jest.js` as resolved from `cwd` (its `exports` map hides the bin subpath, so derive it). */
|
|
42
|
+
function findJestBin(cwd) {
|
|
43
|
+
try {
|
|
44
|
+
const entry = createRequire(join(resolve(cwd), 'noop.js')).resolve('jest');
|
|
45
|
+
const root = jestPackageRoot(entry);
|
|
46
|
+
if (root) {
|
|
47
|
+
const bin = join(root, 'bin', 'jest.js');
|
|
48
|
+
if (existsSync(bin)) return bin;
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
/* fall through to the walk */
|
|
52
|
+
}
|
|
53
|
+
let dir = resolve(cwd);
|
|
54
|
+
for (;;) {
|
|
55
|
+
const bin = join(dir, 'node_modules', 'jest', 'bin', 'jest.js');
|
|
56
|
+
if (existsSync(bin)) return bin;
|
|
57
|
+
const parent = resolve(dir, '..');
|
|
58
|
+
if (parent === dir) return null;
|
|
59
|
+
dir = parent;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Signal handling, as npm and cross-spawn do it:
|
|
65
|
+
* - SIGINT is never forwarded: a Ctrl+C reaches jest from the terminal itself (the console process group
|
|
66
|
+
* on POSIX, CTRL_C_EVENT on Windows), so forwarding would deliver it twice. The launcher only ignores
|
|
67
|
+
* it and waits for jest to finish its own shutdown.
|
|
68
|
+
* - SIGTERM and SIGHUP are forwarded on POSIX (a supervisor signals the launcher, not the group).
|
|
69
|
+
* - On Windows nothing is forwarded: `child.kill(signal)` there is TerminateProcess, which would cut
|
|
70
|
+
* jest's own cleanup short.
|
|
71
|
+
*/
|
|
72
|
+
const IGNORED_SIGNALS = ['SIGINT'];
|
|
73
|
+
const FORWARDED_SIGNALS = process.platform === 'win32' ? [] : ['SIGTERM', 'SIGHUP'];
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
const jestBin = findJestBin(process.cwd());
|
|
77
|
+
if (!jestBin) {
|
|
78
|
+
process.stderr.write(
|
|
79
|
+
`mcp-core-jest: could not locate jest from ${process.cwd()}. Add jest as a devDependency of this package and install it.\n`,
|
|
80
|
+
);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const child = spawn(process.execPath, ['--experimental-vm-modules', jestBin, ...process.argv.slice(2)], {
|
|
84
|
+
stdio: 'inherit',
|
|
85
|
+
shell: false,
|
|
86
|
+
});
|
|
87
|
+
const forwarders = [
|
|
88
|
+
...IGNORED_SIGNALS.map((signal) => [signal, () => undefined]),
|
|
89
|
+
...FORWARDED_SIGNALS.map((signal) => [
|
|
90
|
+
signal,
|
|
91
|
+
() => {
|
|
92
|
+
if (child.exitCode === null && child.signalCode === null) child.kill(signal);
|
|
93
|
+
},
|
|
94
|
+
]),
|
|
95
|
+
];
|
|
96
|
+
for (const [signal, handler] of forwarders) process.on(signal, handler);
|
|
97
|
+
child.on('error', (err) => {
|
|
98
|
+
process.stderr.write(`mcp-core-jest: could not start jest: ${err.message}\n`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
|
101
|
+
child.on('exit', (code, signal) => {
|
|
102
|
+
for (const [name, forward] of forwarders) process.off(name, forward);
|
|
103
|
+
if (signal) {
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
process.kill(process.pid, signal);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
process.exit(code ?? 1);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
main();
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - HTTP auth
|
|
3
|
+
*
|
|
4
|
+
* mcp-core's HTTP auth runs on the SDK's resource-server gate: `verifyBearerToken` parses the
|
|
5
|
+
* `Authorization` header, calls an `OAuthTokenVerifier`, checks `expectedResource`, `requiredScopes` and
|
|
6
|
+
* the expiry, and `bearerAuthChallengeResponse` maps a failure to the 401 / 403 OAuth error with its
|
|
7
|
+
* `WWW-Authenticate` challenge. The verified `AuthInfo` is set as `req.auth`, which the SDK forwards to
|
|
8
|
+
* the server factory (`RequestContext.authInfo`) and to handlers (`ctx.http.authInfo`), so per-tool
|
|
9
|
+
* `scopeChallenge` / `requireScopes(...)` gates work.
|
|
10
|
+
*
|
|
11
|
+
* The static `MCP_API_KEY` is one verifier (`apiKeyVerifier`): a constant-time comparison (SHA-256 of
|
|
12
|
+
* both sides, `timingSafeEqual`) that answers an `AuthInfo` whose `token` is a placeholder, never the
|
|
13
|
+
* key. A host can pass its own verifier (for example one checking OAuth JWTs) instead.
|
|
14
|
+
*/
|
|
15
|
+
import type { ServerResponse } from 'node:http';
|
|
16
|
+
import { type AuthInfo, type OAuthTokenVerifier, type VerifyBearerTokenOptions } from '@modelcontextprotocol/server';
|
|
17
|
+
/** The `AuthInfo.token` the API-key verifier reports: never the real key. */
|
|
18
|
+
export declare const API_KEY_TOKEN_PLACEHOLDER = "[api-key]";
|
|
19
|
+
/** How long an API-key `AuthInfo` is valid (the SDK refuses tokens without `expiresAt`). */
|
|
20
|
+
export declare const API_KEY_TTL_SECONDS = 3600;
|
|
21
|
+
export interface ApiKeyVerifierOptions {
|
|
22
|
+
/** `AuthInfo.clientId` (default `api-key`). */
|
|
23
|
+
clientId?: string;
|
|
24
|
+
/** `AuthInfo.scopes` granted by the key (default none). */
|
|
25
|
+
scopes?: string[];
|
|
26
|
+
/** `AuthInfo.expiresAt` = now + this (default 3600 s). */
|
|
27
|
+
ttlSeconds?: number;
|
|
28
|
+
}
|
|
29
|
+
/** Options for the HTTP auth gate (transport and Express adapter). */
|
|
30
|
+
export interface HttpAuthOptions {
|
|
31
|
+
/** A token verifier (for example a JWT verifier). When set it replaces the static API key. */
|
|
32
|
+
verifier?: OAuthTokenVerifier;
|
|
33
|
+
/** Scopes every request must carry (403 `insufficient_scope` otherwise). */
|
|
34
|
+
requiredScopes?: string[];
|
|
35
|
+
/** RFC 9728 Protected Resource Metadata URL advertised in challenges. */
|
|
36
|
+
resourceMetadataUrl?: string;
|
|
37
|
+
/** Accept only tokens issued for this resource (audience). */
|
|
38
|
+
expectedResource?: URL;
|
|
39
|
+
/** `AuthInfo` fields for the static API key. */
|
|
40
|
+
apiKey?: ApiKeyVerifierOptions;
|
|
41
|
+
}
|
|
42
|
+
/** A verifier for one static key, compared in constant time. The returned `AuthInfo` never holds the key. */
|
|
43
|
+
export declare function apiKeyVerifier(apiKey: string, options?: ApiKeyVerifierOptions): OAuthTokenVerifier;
|
|
44
|
+
/** The SDK bearer options for a gate: the explicit verifier, else the API key, else none (open). */
|
|
45
|
+
export declare function bearerOptions(auth: HttpAuthOptions | undefined, apiKey: string | undefined): VerifyBearerTokenOptions | undefined;
|
|
46
|
+
export type AuthOutcome = {
|
|
47
|
+
ok: true;
|
|
48
|
+
authInfo: AuthInfo;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
response: Response;
|
|
52
|
+
};
|
|
53
|
+
/** Verify an `Authorization` header value; a failure carries the SDK's ready-made challenge response. */
|
|
54
|
+
export declare function authenticate(header: string | undefined, options: VerifyBearerTokenOptions): Promise<AuthOutcome>;
|
|
55
|
+
/** Write a web `Response` (status, headers, body) to a Node response. */
|
|
56
|
+
export declare function writeWebResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
57
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,KAAK,QAAQ,EAIb,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAE9B,MAAM,8BAA8B,CAAC;AAEtC,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,gDAAgD;IAChD,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAMD,6GAA6G;AAC7G,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,kBAAkB,CAgBtG;AAED,oGAAoG;AACpG,wBAAgB,aAAa,CAC3B,IAAI,EAAE,eAAe,GAAG,SAAS,EACjC,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,wBAAwB,GAAG,SAAS,CAStC;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE/F,yGAAyG;AACzG,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,WAAW,CAAC,CAMtB;AAED,yEAAyE;AACzE,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ7F"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - HTTP auth
|
|
3
|
+
*
|
|
4
|
+
* mcp-core's HTTP auth runs on the SDK's resource-server gate: `verifyBearerToken` parses the
|
|
5
|
+
* `Authorization` header, calls an `OAuthTokenVerifier`, checks `expectedResource`, `requiredScopes` and
|
|
6
|
+
* the expiry, and `bearerAuthChallengeResponse` maps a failure to the 401 / 403 OAuth error with its
|
|
7
|
+
* `WWW-Authenticate` challenge. The verified `AuthInfo` is set as `req.auth`, which the SDK forwards to
|
|
8
|
+
* the server factory (`RequestContext.authInfo`) and to handlers (`ctx.http.authInfo`), so per-tool
|
|
9
|
+
* `scopeChallenge` / `requireScopes(...)` gates work.
|
|
10
|
+
*
|
|
11
|
+
* The static `MCP_API_KEY` is one verifier (`apiKeyVerifier`): a constant-time comparison (SHA-256 of
|
|
12
|
+
* both sides, `timingSafeEqual`) that answers an `AuthInfo` whose `token` is a placeholder, never the
|
|
13
|
+
* key. A host can pass its own verifier (for example one checking OAuth JWTs) instead.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
16
|
+
import { bearerAuthChallengeResponse, OAuthError, OAuthErrorCode, verifyBearerToken, } from '@modelcontextprotocol/server';
|
|
17
|
+
/** The `AuthInfo.token` the API-key verifier reports: never the real key. */
|
|
18
|
+
export const API_KEY_TOKEN_PLACEHOLDER = '[api-key]';
|
|
19
|
+
/** How long an API-key `AuthInfo` is valid (the SDK refuses tokens without `expiresAt`). */
|
|
20
|
+
export const API_KEY_TTL_SECONDS = 3600;
|
|
21
|
+
function digest(value) {
|
|
22
|
+
return createHash('sha256').update(value).digest();
|
|
23
|
+
}
|
|
24
|
+
/** A verifier for one static key, compared in constant time. The returned `AuthInfo` never holds the key. */
|
|
25
|
+
export function apiKeyVerifier(apiKey, options = {}) {
|
|
26
|
+
if (apiKey.length === 0)
|
|
27
|
+
throw new Error('apiKeyVerifier: the API key must not be empty');
|
|
28
|
+
const expected = digest(apiKey);
|
|
29
|
+
return {
|
|
30
|
+
async verifyAccessToken(token) {
|
|
31
|
+
if (!timingSafeEqual(digest(token), expected)) {
|
|
32
|
+
throw new OAuthError(OAuthErrorCode.InvalidToken, 'Invalid token');
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
token: API_KEY_TOKEN_PLACEHOLDER,
|
|
36
|
+
clientId: options.clientId ?? 'api-key',
|
|
37
|
+
scopes: [...(options.scopes ?? [])],
|
|
38
|
+
expiresAt: Math.floor(Date.now() / 1000) + (options.ttlSeconds ?? API_KEY_TTL_SECONDS),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** The SDK bearer options for a gate: the explicit verifier, else the API key, else none (open). */
|
|
44
|
+
export function bearerOptions(auth, apiKey) {
|
|
45
|
+
const verifier = auth?.verifier ?? (apiKey ? apiKeyVerifier(apiKey, auth?.apiKey) : undefined);
|
|
46
|
+
if (!verifier)
|
|
47
|
+
return undefined;
|
|
48
|
+
return {
|
|
49
|
+
verifier,
|
|
50
|
+
...(auth?.requiredScopes ? { requiredScopes: auth.requiredScopes } : {}),
|
|
51
|
+
...(auth?.resourceMetadataUrl ? { resourceMetadataUrl: auth.resourceMetadataUrl } : {}),
|
|
52
|
+
...(auth?.expectedResource ? { expectedResource: auth.expectedResource } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Verify an `Authorization` header value; a failure carries the SDK's ready-made challenge response. */
|
|
56
|
+
export async function authenticate(header, options) {
|
|
57
|
+
try {
|
|
58
|
+
return { ok: true, authInfo: await verifyBearerToken(header, options) };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return { ok: false, response: bearerAuthChallengeResponse(error, options) };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Write a web `Response` (status, headers, body) to a Node response. */
|
|
65
|
+
export async function writeWebResponse(res, response) {
|
|
66
|
+
const headers = {};
|
|
67
|
+
response.headers.forEach((value, key) => {
|
|
68
|
+
headers[key] = value;
|
|
69
|
+
});
|
|
70
|
+
const body = await response.text();
|
|
71
|
+
res.writeHead(response.status, headers);
|
|
72
|
+
res.end(body);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|