@casys/mcp-server 0.2.1

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 ADDED
@@ -0,0 +1,417 @@
1
+ # @casys/mcp-server
2
+
3
+ [![npm](https://img.shields.io/npm/v/@casys/mcp-server)](https://www.npmjs.com/package/@casys/mcp-server)
4
+ [![JSR](https://jsr.io/badges/@casys/mcp-server)](https://jsr.io/@casys/mcp-server)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ **The "Hono for MCP"** — a production-grade framework for building Model Context Protocol servers in TypeScript.
8
+
9
+ Composable middleware, OAuth2 auth, dual transport, observability, and everything you need to ship reliable MCP servers. Built on the official [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/sdk).
10
+
11
+ ```
12
+ rate-limit → auth → custom middleware → scope-check → validation → backpressure → handler
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Why @casys/mcp-server?
18
+
19
+ The official SDK gives you the protocol. This framework gives you the production stack.
20
+
21
+ | | Official SDK | @casys/mcp-server |
22
+ | ----------------------- | :----------: | :----------------------------: |
23
+ | MCP protocol compliance | Yes | Yes |
24
+ | Concurrency control | -- | 3 backpressure strategies |
25
+ | Middleware pipeline | -- | Composable onion model |
26
+ | OAuth2 / JWT auth | -- | Built-in + 4 OIDC presets |
27
+ | Rate limiting | -- | Sliding window, per-client |
28
+ | Schema validation | -- | JSON Schema (ajv) |
29
+ | Streamable HTTP + SSE | Manual | Built-in session management |
30
+ | OpenTelemetry tracing | -- | Automatic spans per tool call |
31
+ | Prometheus metrics | -- | `/metrics` endpoint |
32
+ | MCP Apps (UI resources) | Manual | `registerResource()` + `ui://` |
33
+ | Sampling bridge | -- | Bidirectional LLM delegation |
34
+
35
+ ---
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ # npm
41
+ npm install @casys/mcp-server
42
+
43
+ # Deno
44
+ deno add jsr:@casys/mcp-server
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Quick Start
50
+
51
+ ### STDIO Server (5 lines)
52
+
53
+ ```typescript
54
+ import { ConcurrentMCPServer } from "@casys/mcp-server";
55
+
56
+ const server = new ConcurrentMCPServer({ name: "my-server", version: "1.0.0" });
57
+
58
+ server.registerTool(
59
+ {
60
+ name: "greet",
61
+ description: "Greet a user",
62
+ inputSchema: {
63
+ type: "object",
64
+ properties: { name: { type: "string" } },
65
+ required: ["name"],
66
+ },
67
+ },
68
+ ({ name }) => `Hello, ${name}!`,
69
+ );
70
+
71
+ await server.start();
72
+ ```
73
+
74
+ ### HTTP Server with Auth
75
+
76
+ ```typescript
77
+ import {
78
+ ConcurrentMCPServer,
79
+ createGoogleAuthProvider,
80
+ } from "@casys/mcp-server";
81
+
82
+ const server = new ConcurrentMCPServer({
83
+ name: "my-api",
84
+ version: "1.0.0",
85
+ maxConcurrent: 10,
86
+ backpressureStrategy: "queue",
87
+ validateSchema: true,
88
+ rateLimit: { maxRequests: 100, windowMs: 60_000 },
89
+ auth: {
90
+ provider: createGoogleAuthProvider({
91
+ audience: "https://my-mcp.example.com",
92
+ resource: "https://my-mcp.example.com",
93
+ }),
94
+ },
95
+ });
96
+
97
+ server.registerTool(
98
+ {
99
+ name: "query",
100
+ description: "Query the database",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: { sql: { type: "string" } },
104
+ },
105
+ requiredScopes: ["db:read"],
106
+ },
107
+ async ({ sql }) => ({ rows: [] }),
108
+ );
109
+
110
+ await server.startHttp({ port: 3000 });
111
+ // GET /health → { status: "ok" }
112
+ // GET /metrics → Prometheus text format
113
+ // POST /mcp → JSON-RPC (tools/call, tools/list, ...)
114
+ // GET /mcp → SSE stream (server→client notifications)
115
+ ```
116
+
117
+ **Secure-by-default HTTP options:**
118
+
119
+ ```typescript
120
+ await server.startHttp({
121
+ port: 3000,
122
+ requireAuth: true, // fail fast if auth isn't configured
123
+ corsOrigins: ["https://app.example.com"],
124
+ maxBodyBytes: 1_000_000, // 1 MB
125
+ ipRateLimit: { maxRequests: 120, windowMs: 60_000 },
126
+ });
127
+ ```
128
+
129
+ **Notes:**
130
+
131
+ - `requireAuth: true` throws if no auth provider is configured
132
+ - `corsOrigins` defaults to `"*"` — use an allowlist in production
133
+ - `maxBodyBytes` defaults to **1 MB** (set `null` to disable)
134
+ - `ipRateLimit` keys on client IP by default
135
+
136
+ ---
137
+
138
+ ## Features
139
+
140
+ ### Middleware Pipeline
141
+
142
+ Composable onion model — same mental model as Hono, Koa, or Express.
143
+
144
+ ```typescript
145
+ import type { Middleware } from "@casys/mcp-server";
146
+
147
+ const timing: Middleware = async (ctx, next) => {
148
+ const start = performance.now();
149
+ const result = await next();
150
+ console.log(
151
+ `${ctx.toolName} took ${(performance.now() - start).toFixed(0)}ms`,
152
+ );
153
+ return result;
154
+ };
155
+
156
+ server.use(timing);
157
+ ```
158
+
159
+ Built-in pipeline: `rate-limit → auth → custom → scope-check → validation → backpressure → handler`
160
+
161
+ ### OAuth2 / JWT Auth
162
+
163
+ Four OIDC presets out of the box:
164
+
165
+ ```typescript
166
+ import {
167
+ createGoogleAuthProvider, // Google OIDC
168
+ createAuth0AuthProvider, // Auth0
169
+ createGitHubAuthProvider, // GitHub Actions OIDC
170
+ createOIDCAuthProvider, // Generic OIDC (Keycloak, Okta, etc.)
171
+ } from "@casys/mcp-server";
172
+
173
+ const auth0 = createAuth0AuthProvider({
174
+ domain: "my-tenant.auth0.com",
175
+ audience: "https://my-mcp.example.com",
176
+ resource: "https://my-mcp.example.com",
177
+ scopesSupported: ["read", "write"],
178
+ });
179
+ ```
180
+
181
+ Or use `JwtAuthProvider` directly for custom setups:
182
+
183
+ ```typescript
184
+ import { JwtAuthProvider } from "@casys/mcp-server";
185
+
186
+ const provider = new JwtAuthProvider({
187
+ issuer: "https://my-idp.example.com",
188
+ audience: "https://my-mcp.example.com",
189
+ resource: "https://my-mcp.example.com",
190
+ authorizationServers: ["https://my-idp.example.com"],
191
+ });
192
+ ```
193
+
194
+ Token verification is cached (SHA-256 hash → AuthInfo, TTL = min(token expiry, 5min)) to avoid redundant JWKS round-trips.
195
+
196
+ ### YAML + Env Config
197
+
198
+ For binary distribution — users configure auth without code:
199
+
200
+ ```yaml
201
+ # mcp-server.yaml
202
+ auth:
203
+ provider: auth0
204
+ audience: https://my-mcp.example.com
205
+ resource: https://my-mcp.example.com
206
+ domain: my-tenant.auth0.com
207
+ scopesSupported: [read, write, admin]
208
+ ```
209
+
210
+ Env vars override YAML at deploy time:
211
+
212
+ ```bash
213
+ MCP_AUTH_AUDIENCE=https://prod.example.com ./my-server --http --port 3000
214
+ ```
215
+
216
+ Priority: `programmatic > env vars > YAML > no auth`
217
+
218
+ ### RFC 9728
219
+
220
+ When auth is configured, the framework automatically exposes `GET /.well-known/oauth-protected-resource` per [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728).
221
+
222
+ ### Observability
223
+
224
+ Every tool call emits an **OpenTelemetry span** with rich attributes:
225
+
226
+ ```
227
+ mcp.tool.call query
228
+ mcp.tool.name = "query"
229
+ mcp.server.name = "my-api"
230
+ mcp.transport = "http"
231
+ mcp.session.id = "a1b2c3..."
232
+ mcp.tool.duration_ms = 42
233
+ mcp.tool.success = true
234
+ ```
235
+
236
+ Enable with Deno's native OTEL support:
237
+
238
+ ```bash
239
+ OTEL_DENO=true deno run --unstable-otel server.ts
240
+ ```
241
+
242
+ The HTTP server exposes a **Prometheus-compatible** `/metrics` endpoint:
243
+
244
+ ```
245
+ mcp_server_tool_calls_total 1024
246
+ mcp_server_tool_calls_success_total 1018
247
+ mcp_server_tool_calls_failed_total 6
248
+ mcp_server_tool_call_duration_ms_bucket{le="50"} 892
249
+ mcp_server_tool_call_duration_ms_bucket{le="100"} 987
250
+ mcp_server_tool_calls_by_name{tool="query",status="success"} 512
251
+ mcp_server_active_requests 3
252
+ mcp_server_active_sessions 42
253
+ mcp_server_sse_clients 7
254
+ mcp_server_uptime_seconds 86400
255
+ ```
256
+
257
+ Programmatic access:
258
+
259
+ ```typescript
260
+ server.getServerMetrics(); // Full snapshot (counters, histograms, gauges)
261
+ server.getPrometheusMetrics(); // Prometheus text format string
262
+ ```
263
+
264
+ ### Concurrency Control
265
+
266
+ Three backpressure strategies when the server is at capacity:
267
+
268
+ | Strategy | Behavior |
269
+ | ----------------- | ------------------------------------------ |
270
+ | `sleep` (default) | Busy-wait with configurable sleep interval |
271
+ | `queue` | FIFO queue with ordered release |
272
+ | `reject` | Fail fast with immediate error |
273
+
274
+ ```typescript
275
+ new ConcurrentMCPServer({
276
+ maxConcurrent: 10,
277
+ backpressureStrategy: "queue",
278
+ });
279
+ ```
280
+
281
+ ### Rate Limiting
282
+
283
+ Sliding window rate limiter with per-client tracking:
284
+
285
+ ```typescript
286
+ new ConcurrentMCPServer({
287
+ rateLimit: {
288
+ maxRequests: 100,
289
+ windowMs: 60_000,
290
+ keyExtractor: (ctx) => ctx.args.clientId as string,
291
+ onLimitExceeded: "wait", // or "reject"
292
+ },
293
+ });
294
+ ```
295
+
296
+ For HTTP endpoints, use `startHttp({ ipRateLimit: ... })` to rate limit by client IP (or custom key).
297
+
298
+ ### Security Best Practices (Tool Handlers)
299
+
300
+ Tool handlers receive **untrusted JSON input**. Treat args as hostile:
301
+
302
+ - **Define strict schemas**: `additionalProperties: false`, `minLength`, `pattern`, `enum`.
303
+ - **Never pass raw args to a shell** (`Deno.Command`, `child_process.exec`). If you must, use an allowlist + argv array (no shell).
304
+ - **Validate paths & resources**: allowlisted roots, deny `..`, restrict env access.
305
+ - **Prefer safe APIs**: parameterized DB queries, SDK methods, typed clients.
306
+ - **Log sensitive actions**: file writes, network calls, admin ops.
307
+
308
+ ### MCP Apps (UI Resources)
309
+
310
+ Register interactive UIs as MCP resources:
311
+
312
+ ```typescript
313
+ import { ConcurrentMCPServer, MCP_APP_MIME_TYPE } from "@casys/mcp-server";
314
+
315
+ server.registerResource(
316
+ { uri: "ui://my-server/viewer", name: "Data Viewer" },
317
+ async (uri) => ({
318
+ uri: uri.toString(),
319
+ mimeType: MCP_APP_MIME_TYPE,
320
+ text: "<html><body>...</body></html>",
321
+ }),
322
+ );
323
+ ```
324
+
325
+ ---
326
+
327
+ ## API Reference
328
+
329
+ ### ConcurrentMCPServer
330
+
331
+ ```typescript
332
+ const server = new ConcurrentMCPServer(options: ConcurrentServerOptions);
333
+
334
+ // Registration (before start)
335
+ server.registerTool(tool, handler);
336
+ server.registerTools(tools, handlers);
337
+ server.registerResource(resource, handler);
338
+ server.registerResources(resources, handlers);
339
+ server.use(middleware);
340
+
341
+ // Transport
342
+ await server.start(); // STDIO
343
+ await server.startHttp({ port: 3000 }); // HTTP + SSE
344
+ await server.stop(); // Graceful shutdown
345
+
346
+ // Observability
347
+ server.getMetrics(); // { inFlight, queued }
348
+ server.getServerMetrics(); // Full snapshot
349
+ server.getPrometheusMetrics(); // Prometheus text format
350
+ server.getRateLimitMetrics(); // { keys, totalRequests }
351
+
352
+ // Introspection
353
+ server.getToolCount();
354
+ server.getToolNames();
355
+ server.getResourceCount();
356
+ server.getResourceUris();
357
+ server.getSSEClientCount();
358
+
359
+ // SSE (Streamable HTTP)
360
+ server.sendToSession(sessionId, message);
361
+ server.broadcastNotification(method, params);
362
+ ```
363
+
364
+ ### Standalone Components
365
+
366
+ Each component works independently:
367
+
368
+ ```typescript
369
+ import { RateLimiter, RequestQueue, SchemaValidator } from "@casys/mcp-server";
370
+
371
+ // Rate limiter
372
+ const limiter = new RateLimiter({ maxRequests: 10, windowMs: 1000 });
373
+ if (limiter.checkLimit("client-123")) {
374
+ /* proceed */
375
+ }
376
+
377
+ // Request queue
378
+ const queue = new RequestQueue({
379
+ maxConcurrent: 5,
380
+ strategy: "queue",
381
+ sleepMs: 10,
382
+ });
383
+ await queue.acquire();
384
+ try {
385
+ /* work */
386
+ } finally {
387
+ queue.release();
388
+ }
389
+
390
+ // Schema validator
391
+ const validator = new SchemaValidator();
392
+ validator.addSchema("tool", {
393
+ type: "object",
394
+ properties: { n: { type: "number" } },
395
+ });
396
+ validator.validate("tool", { n: 5 }); // { valid: true, errors: [] }
397
+ ```
398
+
399
+ ---
400
+
401
+ ## HTTP Endpoints
402
+
403
+ When running with `startHttp()`:
404
+
405
+ | Method | Path | Description |
406
+ | ------ | --------------------------------------- | ----------------------------------------------------------- |
407
+ | `POST` | `/mcp` or `/` | JSON-RPC endpoint (initialize, tools/call, tools/list, ...) |
408
+ | `GET` | `/mcp` or `/` | SSE stream (server→client notifications) |
409
+ | `GET` | `/health` | Health check |
410
+ | `GET` | `/metrics` | Prometheus metrics |
411
+ | `GET` | `/.well-known/oauth-protected-resource` | RFC 9728 metadata (when auth enabled) |
412
+
413
+ ---
414
+
415
+ ## License
416
+
417
+ MIT
package/mod.ts ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * MCP Concurrent Server Framework
3
+ *
4
+ * Production-ready MCP server framework with built-in concurrency control,
5
+ * backpressure strategies, and optional sampling support.
6
+ *
7
+ * Built on top of the official @modelcontextprotocol/sdk with added
8
+ * production features for reliability and performance.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { ConcurrentMCPServer } from "@casys/mcp-server";
13
+ *
14
+ * const server = new ConcurrentMCPServer({
15
+ * name: "my-server",
16
+ * version: "1.0.0",
17
+ * maxConcurrent: 10,
18
+ * backpressureStrategy: 'queue'
19
+ * });
20
+ *
21
+ * server.registerTool(
22
+ * { name: "greet", description: "Greet someone", inputSchema: { type: "object" } },
23
+ * (args) => `Hello, ${args.name}!`,
24
+ * );
25
+ *
26
+ * // STDIO transport
27
+ * await server.start();
28
+ *
29
+ * // — or — HTTP transport with security-first defaults
30
+ * const http = await server.startHttp({
31
+ * port: 3000,
32
+ * maxBodyBytes: 1_048_576, // 1 MB (default)
33
+ * corsOrigins: ["https://app.example.com"], // allowlist
34
+ * requireAuth: true, // fail-fast without auth
35
+ * ipRateLimit: { maxRequests: 60, windowMs: 60_000 },
36
+ * });
37
+ * ```
38
+ *
39
+ * @module @casys/mcp-server
40
+ */
41
+
42
+ // Main server class
43
+ export { ConcurrentMCPServer } from "./src/concurrent-server.js";
44
+
45
+ // Concurrency primitives
46
+ export { RequestQueue } from "./src/concurrency/request-queue.js";
47
+
48
+ // Rate limiting
49
+ export { RateLimiter } from "./src/concurrency/rate-limiter.js";
50
+
51
+ // Schema validation
52
+ export { SchemaValidator } from "./src/validation/schema-validator.js";
53
+ export type {
54
+ ValidationError,
55
+ ValidationResult,
56
+ } from "./src/validation/schema-validator.js";
57
+
58
+ // Sampling support
59
+ export { SamplingBridge } from "./src/sampling/sampling-bridge.js";
60
+
61
+ // Type exports
62
+ export type {
63
+ ConcurrentServerOptions,
64
+ HttpRateLimitContext,
65
+ HttpRateLimitOptions,
66
+ HttpServerInstance,
67
+ // HTTP Server types
68
+ HttpServerOptions,
69
+ // MCP Apps types (SEP-1865)
70
+ MCPResource,
71
+ MCPTool,
72
+ MCPToolMeta,
73
+ McpUiToolMeta,
74
+ QueueMetrics,
75
+ RateLimitContext,
76
+ RateLimitOptions,
77
+ ResourceContent,
78
+ ResourceHandler,
79
+ SamplingClient,
80
+ SamplingParams,
81
+ SamplingResult,
82
+ ToolHandler,
83
+ } from "./src/types.js";
84
+
85
+ // MCP Apps constants
86
+ export { MCP_APP_MIME_TYPE } from "./src/types.js";
87
+
88
+ // Middleware pipeline
89
+ export type {
90
+ Middleware,
91
+ MiddlewareContext,
92
+ MiddlewareResult,
93
+ NextFunction,
94
+ } from "./src/middleware/mod.js";
95
+ export { createMiddlewareRunner } from "./src/middleware/mod.js";
96
+
97
+ // Auth - Core
98
+ export { AuthProvider } from "./src/auth/mod.js";
99
+ export {
100
+ AuthError,
101
+ createAuthMiddleware,
102
+ createForbiddenResponse,
103
+ createUnauthorizedResponse,
104
+ extractBearerToken,
105
+ } from "./src/auth/mod.js";
106
+ export { createScopeMiddleware } from "./src/auth/mod.js";
107
+ export type {
108
+ AuthInfo,
109
+ AuthOptions,
110
+ ProtectedResourceMetadata,
111
+ } from "./src/auth/mod.js";
112
+
113
+ // Auth - JWT Provider + Presets
114
+ export { JwtAuthProvider } from "./src/auth/mod.js";
115
+ export type { JwtAuthProviderOptions } from "./src/auth/mod.js";
116
+ export {
117
+ createAuth0AuthProvider,
118
+ createGitHubAuthProvider,
119
+ createGoogleAuthProvider,
120
+ createOIDCAuthProvider,
121
+ } from "./src/auth/mod.js";
122
+ export type { PresetOptions } from "./src/auth/mod.js";
123
+
124
+ // Auth - Config (YAML + env)
125
+ export {
126
+ createAuthProviderFromConfig,
127
+ loadAuthConfig,
128
+ } from "./src/auth/mod.js";
129
+ export type { AuthConfig, AuthProviderName } from "./src/auth/mod.js";
130
+
131
+ // Observability
132
+ export {
133
+ endToolCallSpan,
134
+ getServerTracer,
135
+ isOtelEnabled,
136
+ recordAuthEvent,
137
+ ServerMetrics,
138
+ type ServerMetricsSnapshot,
139
+ startToolCallSpan,
140
+ type ToolCallSpanAttributes,
141
+ } from "./src/observability/mod.js";
142
+
143
+ // Security - CSP utilities
144
+ export { buildCspHeader, injectCspMetaTag } from "./src/security/csp.js";
145
+ export type { CspOptions } from "./src/security/csp.js";
146
+
147
+ // Security - HMAC channel authentication for PostMessage (MCP Apps)
148
+ export { injectChannelAuth } from "./src/security/channel-hmac.js";
149
+ export { MessageSigner } from "./src/security/message-signer.js";
150
+ export type {
151
+ SignedMessage,
152
+ VerifyResult,
153
+ } from "./src/security/message-signer.js";
154
+
155
+ // Runtime port (for advanced consumers who need to inspect the adapter contract)
156
+ export type {
157
+ FetchHandler,
158
+ RuntimePort,
159
+ ServeHandle,
160
+ ServeOptions,
161
+ } from "./src/runtime/types.js";
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@casys/mcp-server",
3
+ "version": "0.2.1",
4
+ "description": "Production-ready MCP server framework with concurrency control, auth, and observability",
5
+ "type": "module",
6
+ "main": "mod.ts",
7
+ "types": "mod.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "tsx --test src/**/*_test.ts"
11
+ },
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.15.1",
14
+ "hono": "^4.0.0",
15
+ "ajv": "^8.17.1",
16
+ "jose": "^6.0.0",
17
+ "yaml": "^2.7.0",
18
+ "@opentelemetry/api": "^1.9.0"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.4.0",
22
+ "tsx": "^4.0.0"
23
+ },
24
+ "engines": {
25
+ "node": ">=20.0.0"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/Casys-AI/mcp-server"
30
+ },
31
+ "license": "MIT"
32
+ }