@lynq/lynq 0.8.2 → 0.8.4
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 +116 -0
- package/dist/chunk-CI343GXC.mjs +1 -0
- package/dist/chunk-OYEWQXJU.mjs +1 -0
- package/dist/chunk-YIQZZYBJ.mjs +1 -0
- package/dist/helpers.mjs +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +1 -1
- package/dist/middleware/auth.d.ts +1 -1
- package/dist/middleware/bearer.d.ts +1 -1
- package/dist/middleware/cache.d.ts +2 -2
- package/dist/middleware/cache.mjs +1 -1
- package/dist/middleware/combine.d.ts +1 -1
- package/dist/middleware/credentials.d.ts +1 -1
- package/dist/middleware/guard.d.ts +1 -1
- package/dist/middleware/jwt.d.ts +1 -1
- package/dist/middleware/logger.d.ts +1 -1
- package/dist/middleware/oauth.d.ts +1 -1
- package/dist/middleware/payment.d.ts +1 -1
- package/dist/middleware/rate-limit.d.ts +23 -3
- package/dist/middleware/rate-limit.mjs +1 -1
- package/dist/middleware/retry.d.ts +1 -1
- package/dist/middleware/tip.d.ts +1 -1
- package/dist/middleware/truncate.d.ts +1 -1
- package/dist/middleware/url-action.d.ts +1 -1
- package/dist/store.d.ts +7 -3
- package/dist/store.mjs +1 -1
- package/dist/test.d.ts +20 -2
- package/dist/test.mjs +1 -1
- package/dist/{types-P-hHI9qc.d.ts → types-CB6NartK.d.ts} +21 -4
- package/package.json +1 -1
- package/dist/chunk-3BJEUP3F.mjs +0 -1
- package/dist/chunk-63JN2KYH.mjs +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @lynq/lynq
|
|
2
|
+
|
|
3
|
+
[](https://github.com/hogekai/lynq/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@lynq/lynq)
|
|
5
|
+
|
|
6
|
+
Lightweight MCP server framework. Tool visibility control through middleware.
|
|
7
|
+
|
|
8
|
+
## The Problem
|
|
9
|
+
|
|
10
|
+
With the official SDK, adding session-aware tool visibility requires manual plumbing:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// Without lynq — manual session tracking, manual notifications
|
|
14
|
+
const sessions = new Map();
|
|
15
|
+
|
|
16
|
+
server.setRequestHandler(ListToolsRequestSchema, (req, extra) => {
|
|
17
|
+
const session = sessions.get(extra.sessionId);
|
|
18
|
+
const tools = [loginTool];
|
|
19
|
+
if (session?.authorized) tools.push(weatherTool); // manual filtering
|
|
20
|
+
return { tools };
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
|
|
24
|
+
if (req.params.name === "login") {
|
|
25
|
+
sessions.set(extra.sessionId, { authorized: true });
|
|
26
|
+
server.sendToolListChanged(); // manual notification
|
|
27
|
+
}
|
|
28
|
+
// ...
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## The Solution
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// With lynq — one line
|
|
36
|
+
server.tool("weather", guard(), config, handler);
|
|
37
|
+
// Client gets notified automatically. No manual wiring.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npm install @lynq/lynq
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { createMCPServer } from "@lynq/lynq";
|
|
50
|
+
import { guard } from "@lynq/lynq/guard";
|
|
51
|
+
import { z } from "zod";
|
|
52
|
+
|
|
53
|
+
const server = createMCPServer({ name: "my-server", version: "1.0.0" });
|
|
54
|
+
|
|
55
|
+
server.tool("login", {
|
|
56
|
+
input: z.object({ username: z.string(), password: z.string() }),
|
|
57
|
+
}, async (args, c) => {
|
|
58
|
+
const user = await authenticate(args.username, args.password);
|
|
59
|
+
c.session.set("user", user);
|
|
60
|
+
c.session.authorize("guard");
|
|
61
|
+
return c.text(`Welcome, ${user.name}`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
server.tool("weather", guard(), {
|
|
65
|
+
description: "Get weather for a city",
|
|
66
|
+
input: z.object({ city: z.string() }),
|
|
67
|
+
}, async (args, c) => {
|
|
68
|
+
return c.text(JSON.stringify(await fetchWeather(args.city)));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await server.stdio();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- **Session-Scoped Visibility** — `authorize()` shows tools, `revoke()` hides them. Client notification is automatic.
|
|
77
|
+
- **Hono-Style Middleware** — Global via `server.use()`, per-tool inline. Three hooks: `onRegister`, `onCall`, `onResult`.
|
|
78
|
+
- **Built-in Middleware** — `guard()` `rateLimit()` `logger()` `truncate()` `credentials()` `some()` `every()` `except()`
|
|
79
|
+
- **Response Helpers** — `c.text()` `c.json()` `c.error()` `c.image()` — chainable: `c.text("done").json({ id: 1 })`
|
|
80
|
+
- **Elicitation** — `c.elicit.form(message, zodSchema)` for structured user input. `c.elicit.url()` for external flows.
|
|
81
|
+
- **Framework Adapters** — `server.http()` returns `(Request) => Response`. Mount in Hono, Express, Deno, Workers.
|
|
82
|
+
- **Test Helpers** — `createTestClient()` for in-memory testing. No transport setup.
|
|
83
|
+
- **Tiny Core** — One dependency. ESM only. No config files, no magic.
|
|
84
|
+
|
|
85
|
+
## Middleware Composition
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { guard } from "@lynq/lynq/guard";
|
|
89
|
+
import { rateLimit } from "@lynq/lynq/rate-limit";
|
|
90
|
+
import { logger } from "@lynq/lynq/logger";
|
|
91
|
+
|
|
92
|
+
server.use(logger()); // global
|
|
93
|
+
server.tool("search", guard(), rateLimit({ max: 10 }), config, handler); // per-tool stack
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Ecosystem
|
|
97
|
+
|
|
98
|
+
| Package | Description |
|
|
99
|
+
|---|---|
|
|
100
|
+
| [@lynq/github](https://www.npmjs.com/package/@lynq/github) | GitHub OAuth provider |
|
|
101
|
+
| [@lynq/google](https://www.npmjs.com/package/@lynq/google) | Google OAuth provider |
|
|
102
|
+
| [@lynq/stripe](https://www.npmjs.com/package/@lynq/stripe) | Stripe Checkout payment provider |
|
|
103
|
+
| [@lynq/crypto](https://www.npmjs.com/package/@lynq/crypto) | Crypto payment provider |
|
|
104
|
+
| [@lynq/hono](https://www.npmjs.com/package/@lynq/hono) | Hono framework adapter |
|
|
105
|
+
| [@lynq/express](https://www.npmjs.com/package/@lynq/express) | Express framework adapter |
|
|
106
|
+
| [@lynq/store-redis](https://www.npmjs.com/package/@lynq/store-redis) | Redis Store implementation |
|
|
107
|
+
| [@lynq/store-sqlite](https://www.npmjs.com/package/@lynq/store-sqlite) | SQLite Store implementation |
|
|
108
|
+
| [create-lynq](https://www.npmjs.com/package/create-lynq) | CLI scaffold tool |
|
|
109
|
+
|
|
110
|
+
## Documentation
|
|
111
|
+
|
|
112
|
+
[https://hogekai.github.io/lynq/](https://hogekai.github.io/lynq/)
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var r=new WeakMap;function n(s,e){r.set(s,e);}function t(s){let e=r.get(s);if(!e)throw new Error("No internals registered for this server instance");return e}export{n as a,t as b};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {createHmac,timingSafeEqual}from'crypto';import {normalizeObjectSchema}from'@modelcontextprotocol/sdk/server/zod-compat.js';import {toJsonSchemaCompat}from'@modelcontextprotocol/sdk/server/zod-json-schema-compat.js';function h(e){if(e==null)return {type:"object"};let t=normalizeObjectSchema(e);return t?toJsonSchemaCompat(t):e}function x(e,t){let o=t[t.length-1];if(typeof o!="function")throw new TypeError(`${e}: last argument must be a handler function`);let n=t[t.length-2];if(n==null||typeof n!="object"||Array.isArray(n))throw new TypeError(`${e}: second-to-last argument must be a config object`);let r=t.slice(0,-2);for(let s of r)if(!s||typeof s!="object"||typeof s.name!="string")throw new TypeError(`${e}: each middleware must have a "name" property`);return {middlewares:r,config:n,handler:o}}function T(e,t){let o=[];for(let n of t)n.onRegister?.(e)===false&&o.push(n.name);return o}function R(e){let o=e.split(/\{[^}]+\}/).map(n=>n.replace(/[.*+?^$|()[\]\\]/g,"\\$&"));return new RegExp(`^${o.join("([^/]+)")}$`)}function y(e,t,o,n){let r=o.get(t);if(r==="disabled")return false;if(r==="enabled")return true;for(let s of e)if(!n.has(s))return false;return true}function b(e,t){let o=e.get(t);if(o)return o;for(let n of e.values())if(n.isTemplate&&n.uriPattern?.test(t))return n}function $(e,t,o){let n=`${e}:${t}`,r=createHmac("sha256",o).update(n).digest("hex");return `${n}:${r}`}function C(e,t){if(e.length<66)return null;let o=e.slice(-64);if(e[e.length-65]!==":")return null;let n=e.slice(0,-65),r=n.indexOf(":");if(r<1)return null;let s=n.slice(0,r),i=n.slice(r+1);if(!i)return null;let l=createHmac("sha256",t).update(`${s}:${i}`).digest("hex");try{if(!timingSafeEqual(Buffer.from(o,"hex"),Buffer.from(l,"hex")))return null}catch{return null}return {sessionId:s,elicitationId:i}}function M(e,t){if(!e)return false;let o=e.replace(/:\d+$/,"");return t.includes(o)}var S=["localhost","127.0.0.1","::1"];function I(e,t,o){let n=e.filter(l=>l.onCall),r=e.filter(l=>l.onResult).reverse(),s=0,i=async()=>{if(s>=n.length){let a=await o();for(let c of r)a=await c.onResult(a,t);return a}return n[s++].onCall(t,i)};return i}export{h as a,x as b,T as c,R as d,y as e,b as f,$ as g,C as h,M as i,S as j,I as k};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function g(s){let r=s?.maxEntries??1e4,e=new Map;return {async get(n){let t=e.get(n);if(t){if(t.expiresAt!==void 0&&Date.now()>t.expiresAt){e.delete(n);return}return t.accessedAt=Date.now(),t.value}},async set(n,t,i){if(e.size>=r){let f=Date.now();for(let[o,u]of e)u.expiresAt!==void 0&&f>u.expiresAt&&e.delete(o);if(e.size>=r){let o,u=Number.POSITIVE_INFINITY;for(let[c,d]of e)d.accessedAt<u&&(u=d.accessedAt,o=c);o!==void 0&&e.delete(o);}}e.set(n,{value:t,expiresAt:i!==void 0?Date.now()+i*1e3:void 0,accessedAt:Date.now()});},async delete(n){e.delete(n);}}}function a(s){let r=s.get("user");if(r){if(typeof r=="string")return r;if(typeof r=="object"&&r!==null){let e=r;if(typeof e.id=="string")return e.id;if(typeof e.id=="number")return String(e.id);if(typeof e.sub=="string")return e.sub}}}function p(s,r){let e=()=>{let n=a(s);if(!n){let t=s.get("user");if(t){let i=typeof t=="object"?JSON.stringify(t):typeof t;throw new Error(`userStore: session has a "user" but could not resolve an ID. Expected: string | { id: string | number } | { sub: string }. Got: ${i}`)}throw new Error("userStore requires a user in session. Call session.set('user', ...) first.")}return n};return {async get(n){return r.get(`user:${e()}:${n}`)},async set(n,t,i){await r.set(`user:${e()}:${n}`,t,i);},async delete(n){await r.delete(`user:${e()}:${n}`);}}}export{g as a,a as b,p as c};
|
package/dist/helpers.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{j as LOCALHOST_HOSTS,g as signState,i as validateHost,h as verifyState}from'./chunk-
|
|
1
|
+
export{j as LOCALHOST_HOSTS,g as signState,i as validateHost,h as verifyState}from'./chunk-OYEWQXJU.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as ServerOptions, M as MCPServer } from './types-
|
|
2
|
-
export { E as Elicit, a as ElicitFormResult, b as ElicitUrlOptions, c as ElicitUrlResult, H as HttpAdapterOptions, R as ResourceConfig, d as ResourceContent, e as ResourceContext, f as ResourceHandler, g as RootInfo, h as Sample, i as SampleOptions, j as SampleRawParams, k as SampleRawResult, l as ServerInfo, m as Session, n as Store, T as TaskConfig, o as TaskContext, p as TaskControl, q as TaskHandler, r as ToolConfig, s as ToolContext, t as ToolHandler, u as ToolInfo, v as ToolMiddleware, w as ToolResponse, U as User, x as UserStore, y as error, z as image, A as json, B as text } from './types-
|
|
3
|
-
export { memoryStore } from './store.js';
|
|
1
|
+
import { S as ServerOptions, M as MCPServer } from './types-CB6NartK.js';
|
|
2
|
+
export { E as Elicit, a as ElicitFormResult, b as ElicitUrlOptions, c as ElicitUrlResult, H as HttpAdapterOptions, R as ResourceConfig, d as ResourceContent, e as ResourceContext, f as ResourceHandler, g as RootInfo, h as Sample, i as SampleOptions, j as SampleRawParams, k as SampleRawResult, l as ServerInfo, m as Session, n as Store, T as TaskConfig, o as TaskContext, p as TaskControl, q as TaskHandler, r as ToolConfig, s as ToolContext, t as ToolHandler, u as ToolInfo, v as ToolMiddleware, w as ToolResponse, U as User, x as UserStore, y as error, z as image, A as json, B as text } from './types-CB6NartK.js';
|
|
3
|
+
export { MemoryStoreOptions, memoryStore } from './store.js';
|
|
4
4
|
import '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import 'zod';
|
|
6
6
|
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {b as b$1,c,d,a as a$1,k,f,e}from'./chunk-3BJEUP3F.mjs';import {a,c as c$2}from'./chunk-63JN2KYH.mjs';export{a as memoryStore}from'./chunk-63JN2KYH.mjs';import {c as c$1,d as d$1,b as b$2,a as a$2}from'./chunk-VAAZWX4U.mjs';export{c as error,d as image,b as json,a as text}from'./chunk-VAAZWX4U.mjs';import {Server}from'@modelcontextprotocol/sdk/server/index.js';import {InMemoryTaskStore}from'@modelcontextprotocol/sdk/experimental/tasks';import {ListToolsRequestSchema,CallToolRequestSchema,ListResourcesRequestSchema,ListResourceTemplatesRequestSchema,ReadResourceRequestSchema}from'@modelcontextprotocol/sdk/types.js';function z(){let t=new Map;function e(){let r=Date.now();for(let[c,d]of t)r-d.createdAt>36e5&&t.delete(c);}function o(r,c,d){return e(),new Promise(m=>{let n;try{n=d.createElicitationCompletionNotifier(r);}catch{}t.set(r,{resolver:m,completionNotifier:n,createdAt:Date.now()});})}function s(r){e();let c=t.get(r);c&&(t.delete(r),c.completionNotifier&&c.completionNotifier().catch(()=>{}),c.resolver());}function i(r){let c=t.get(r);c&&(t.delete(r),c.resolver());}return {register:o,complete:s,cancel:i}}function K(t,e,o){return {async form(s,i){let r=a$1(i),c=await t.elicitInput({message:s,requestedSchema:r});return {action:c.action,content:c.content??{}}},async url(s,i,r){let c=r?.elicitationId??crypto.randomUUID(),d;r?.waitForCompletion&&e&&(d=e(c,t));let m=await t.elicitInput({mode:"url",message:s,url:i,elicitationId:c});if(m.action==="accept"&&d){let n=r?.timeout??3e5,g,y=new Promise((T,a)=>{g=setTimeout(()=>a(new Error("Elicitation timed out")),n);});try{await Promise.race([d,y]);}catch(T){throw o&&o(c),T}finally{clearTimeout(g);}}else d&&o&&o(c);return {action:m.action}}}}function D(t){return async()=>{try{return (await t.listRoots()).roots.map(o=>{let s={uri:o.uri};return o.name!==void 0&&(s.name=o.name),s})}catch{return []}}}function Q(t){async function e(s,i){let r={messages:[{role:"user",content:{type:"text",text:s}}],maxTokens:i?.maxTokens??1024};i?.model!==void 0&&(r.modelPreferences={hints:[{name:i.model}]}),i?.system!==void 0&&(r.systemPrompt=i.system),i?.temperature!==void 0&&(r.temperature=i.temperature),i?.stopSequences!==void 0&&(r.stopSequences=i.stopSequences);let d=(await t.createMessage(r)).content;return d.type==="text"?d.text:""}async function o(s){return t.createMessage(s)}return Object.assign(e,{raw:o})}function O(t,e,o,s,i,r,c,d,m){return {toolName:s,args:i,session:o,signal:r,sessionId:e,elicit:K(t,d,m),roots:D(t),sample:Q(t),text:a$2,json:b$2,error:c$1,image:d$1,store:c,userStore:c$2(o,c)}}function b(t,e){let o=t.sessions.get(e);if(!o&&(o={data:new Map,grants:new Set,toolOverrides:new Map,resourceOverrides:new Map},t.sessions.set(e,o),t.onSessionCreate))try{Promise.resolve(t.onSessionCreate(e)).catch(()=>{});}catch{}return o}function P(t,e$1,o){let s=b(t,o);return e(e$1.hiddenByMiddlewares,e$1.name,s.toolOverrides,s.grants)}function C(t,e$1,o){let s=b(t,o);return e(e$1.hiddenByMiddlewares,e$1.uri,s.resourceOverrides,s.grants)}function x(t,e$1,o){let s=b(t,o);return e(e$1.hiddenByMiddlewares,e$1.name,s.toolOverrides,s.grants)}function _(t,e,o){(o&&t.serverBySession.get(o)||e).sendToolListChanged().catch(()=>{});}function A(t,e,o){(o&&t.serverBySession.get(o)||e).sendResourceListChanged().catch(()=>{});}function w(t,e,o){let s=b(t,o);return {get(i){return s.data.get(i)},set(i,r){s.data.set(i,r);},authorize(i){s.grants.add(i),_(t,e,o),A(t,e,o);},revoke(i){s.grants.delete(i),_(t,e,o),A(t,e,o);},enableTools(...i){for(let r of i)s.toolOverrides.set(r,"enabled");_(t,e,o);},disableTools(...i){for(let r of i)s.toolOverrides.set(r,"disabled");_(t,e,o);},enableResources(...i){for(let r of i)s.resourceOverrides.set(r,"enabled");A(t,e,o);},disableResources(...i){for(let r of i)s.resourceOverrides.set(r,"disabled");A(t,e,o);}}}function F(){let t=new Set,e=new InMemoryTaskStore;return {taskStore:new Proxy(e,{get(s,i,r){return i==="updateTaskStatus"?async(c,d,...m)=>(d==="cancelled"&&t.add(c),s.updateTaskStatus.call(s,c,d,...m)):Reflect.get(s,i,r)}}),cancelledTaskIds:t}}function L(t,e,o,s,i){t.setRequestHandler(ListToolsRequestSchema,(r,c)=>{let d=c.sessionId??"default",m=[];for(let n of e.tools.values())P(e,n,d)&&m.push({name:n.name,description:n.description,inputSchema:a$1(n.input)});for(let n of e.tasks.values())x(e,n,d)&&m.push({name:n.name,description:n.description,inputSchema:a$1(n.input),execution:{taskSupport:"required"}});return {tools:m}}),t.setRequestHandler(CallToolRequestSchema,async(r,c)=>{let{name:d,arguments:m}=r.params,n=c.sessionId??"default",g=e.tools.get(d);if(g){if(!P(e,g,n))return c$1(`Tool not available: ${d}`);let T=m??{},a=O(t,n,w(e,o,n),d,T,c.signal,e.store,(f,S)=>s.register(f,n,S),s.cancel),u=()=>Promise.resolve(g.handler(T,a));return k(g.middlewares,a,u)()}let y=e.tasks.get(d);if(y){if(!x(e,y,n))return c$1(`Tool not available: ${d}`);let T=c.taskStore;if(!T)return c$1("Task store not available");let a=await T.createTask({pollInterval:1e3}),u=a.taskId,l={progress(p,h){if(i.has(u))return;let W=h?`${p}% ${h}`:`${p}%`;T.updateTaskStatus(u,"working",W).catch(()=>{});},get cancelled(){return i.has(u)}},f=m??{},S={...O(t,n,w(e,o,n),d,f,c.signal,e.store,(p,h)=>s.register(p,n,h),s.cancel),task:l},v=async()=>((async()=>{try{let p=await y.handler(f,S);i.has(u)||await T.storeTaskResult(u,"completed",p);}catch(p){if(!i.has(u)){let h=p instanceof Error?p.message:String(p);await T.storeTaskResult(u,"failed",c$1(h)).catch(()=>{});}}})(),{task:a});return k(y.middlewares,S,v)()}return c$1(`Unknown tool: ${d}`)}),t.setRequestHandler(ListResourcesRequestSchema,(r,c)=>{let d=c.sessionId??"default",m=[];for(let n of e.resources.values())!n.isTemplate&&C(e,n,d)&&m.push({uri:n.uri,name:n.name,description:n.description,mimeType:n.mimeType});return {resources:m}}),t.setRequestHandler(ListResourceTemplatesRequestSchema,(r,c)=>{let d=c.sessionId??"default",m=[];for(let n of e.resources.values())n.isTemplate&&C(e,n,d)&&m.push({uriTemplate:n.uri,name:n.name,description:n.description,mimeType:n.mimeType});return {resourceTemplates:m}}),t.setRequestHandler(ReadResourceRequestSchema,async(r,c)=>{let{uri:d}=r.params,m=f(e.resources,d);if(!m)throw new Error(`Unknown resource: ${d}`);let n=c.sessionId??"default";if(!C(e,m,n))throw new Error(`Resource not available: ${d}`);let g=w(e,o,n),y={uri:d,session:g,sessionId:n,roots:D(t),store:e.store,userStore:c$2(g,e.store)},T=O(t,n,g,m.uri,{},c.signal,e.store,(l,f)=>s.register(l,n,f),s.cancel),a=async()=>{let l=await m.handler(d,y);return {contents:[{uri:d,mimeType:l.mimeType??m.mimeType,...l.text!=null?{text:l.text}:{},...l.blob!=null?{blob:l.blob}:{}}]}};return k(m.middlewares,T,a)()});}function G(t,e,o,s,i,r,c){let d={tools:{listChanged:true},resources:{listChanged:true},tasks:{list:{},cancel:{},requests:{tools:{call:{}}}}};function m(){let n=new Server(t,{capabilities:d,taskStore:o});return c(n,e,s,i,r),n}return function(g){let y=null;async function T(){return y||(y=(await import('@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js')).WebStandardStreamableHTTPServerTransport),y}if(g?.sessionless)return async l=>{let f=await T(),S=m(),v=new f({sessionIdGenerator:void 0,enableJsonResponse:g?.enableJsonResponse});return await S.connect(v),v.handleRequest(l)};let a=new Map,u=false;return async l=>{!u&&e.onServerStart&&(u=true,Promise.resolve(e.onServerStart()).catch(()=>{}));let f=await T(),S=l.headers.get("mcp-session-id");if(S){let p=a.get(S);return p?(g?.onRequest&&await g.onRequest(l,S,w(e,s,S)),p.transport.handleRequest(l)):new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Session not found"}}),{status:404,headers:{"Content-Type":"application/json"}})}let v=m(),k=new f({sessionIdGenerator:g?.sessionIdGenerator??(()=>crypto.randomUUID()),enableJsonResponse:g?.enableJsonResponse,onsessioninitialized:p=>{a.set(p,{server:v,transport:k}),e.serverBySession.set(p,v),g?.onRequest&&g.onRequest(l,p,w(e,s,p));},onsessionclosed:p=>{a.delete(p),e.serverBySession.delete(p),e.sessions.delete(p),e.onSessionDestroy&&Promise.resolve(e.onSessionDestroy(p)).catch(()=>{});}});return await v.connect(k),v.onclose=()=>{for(let[p,h]of a)if(h.server===v){a.delete(p),e.serverBySession.delete(p),e.sessions.delete(p),e.onSessionDestroy&&Promise.resolve(e.onSessionDestroy(p)).catch(()=>{});break}},k.handleRequest(l)}}}function ne(t){let e={store:t.store??a(),globalMiddlewares:[],tools:new Map,resources:new Map,tasks:new Map,sessions:new Map,serverBySession:new Map,onServerStart:t.onServerStart,onSessionCreate:t.onSessionCreate,onSessionDestroy:t.onSessionDestroy},o=z(),{taskStore:s,cancelledTaskIds:i}=F(),r=new Server(t,{capabilities:{tools:{listChanged:true},resources:{listChanged:true},tasks:{list:{},cancel:{},requests:{tools:{call:{}}}}},taskStore:s});L(r,e,r,o,i),r.onclose=()=>{let a="default";if(e.sessions.delete(a),e.onSessionDestroy)try{Promise.resolve(e.onSessionDestroy(a)).catch(()=>{});}catch{}};function c$1(a){e.globalMiddlewares.push(a);}function d$1(...a){let u=a[0],l=b$1(`tool("${u}")`,a.slice(1));if(typeof l.config.name=="string")throw new TypeError(`tool("${u}"): second-to-last argument must be a config object`);let f=l.config,S=[...e.globalMiddlewares,...l.middlewares],v={name:u,description:f.description,middlewares:S};e.tools.set(u,{name:u,description:f.description,input:f.input,handler:l.handler,middlewares:S,hiddenByMiddlewares:c(v,S)});}function m(...a){let u=a[0],l=b$1(`resource("${u}")`,a.slice(1));if(typeof l.config.name!="string")throw new TypeError(`resource("${u}"): second-to-last argument must be a config object with a "name" property`);let f=l.config,S=[...e.globalMiddlewares,...l.middlewares],v={name:f.name,description:f.description,middlewares:S},k=u.includes("{");e.resources.set(u,{uri:u,isTemplate:k,uriPattern:k?d(u):null,name:f.name,description:f.description,mimeType:f.mimeType,handler:l.handler,middlewares:S,hiddenByMiddlewares:c(v,S)});}function n(...a){let u=a[0],l=b$1(`task("${u}")`,a.slice(1));if(typeof l.config.name=="string")throw new TypeError(`task("${u}"): second-to-last argument must be a config object`);let f=l.config,S=[...e.globalMiddlewares,...l.middlewares],v={name:u,description:f.description,middlewares:S};e.tasks.set(u,{name:u,description:f.description,input:f.input,handler:l.handler,middlewares:S,hiddenByMiddlewares:c(v,S)});}async function g(){let{StdioServerTransport:a}=await import('@modelcontextprotocol/sdk/server/stdio.js'),u=new a;await r.connect(u),e.onServerStart&&await Promise.resolve(e.onServerStart()).catch(()=>{});}async function y(a){await r.connect(a);}let T=G(t,e,s,r,o,i,L);return {use:c$1,tool:d$1,resource:m,task:n,stdio:g,http:T,session:a=>w(e,r,a),completeElicitation:o.complete,store:e.store,connect:y,_server:r,_getSession:a=>b(e,a),_isToolVisible(a,u){let l=e.tools.get(a);return l?P(e,l,u):false},_isResourceVisible(a,u){let l=e.resources.get(a);return l?C(e,l,u):false},_isTaskVisible(a,u){let l=e.tasks.get(a);return l?x(e,l,u):false},_createSessionAPI:a=>w(e,r,a)}}export{ne as createMCPServer};
|
|
1
|
+
import {a as a$1}from'./chunk-CI343GXC.mjs';import {b as b$1,c,d,a as a$2,k as k$1,f,e}from'./chunk-OYEWQXJU.mjs';import {a,c as c$1}from'./chunk-YIQZZYBJ.mjs';export{a as memoryStore}from'./chunk-YIQZZYBJ.mjs';import {d as d$1,c as c$2,b as b$2,a as a$3}from'./chunk-VAAZWX4U.mjs';export{c as error,d as image,b as json,a as text}from'./chunk-VAAZWX4U.mjs';import {Server}from'@modelcontextprotocol/sdk/server/index.js';import {InMemoryTaskStore}from'@modelcontextprotocol/sdk/experimental/tasks';import {ListToolsRequestSchema,CallToolRequestSchema,McpError,ErrorCode,ListResourcesRequestSchema,ListResourceTemplatesRequestSchema,ReadResourceRequestSchema}from'@modelcontextprotocol/sdk/types.js';function K(){let t=new Map;function e(){let o=Date.now();for(let[l,c]of t)o-c.createdAt>36e5&&t.delete(l);}function r(o,l,c){return e(),new Promise(m=>{let n;try{n=c.createElicitationCompletionNotifier(o);}catch{}t.set(o,{resolver:m,completionNotifier:n,createdAt:Date.now()});})}function s(o){e();let l=t.get(o);l&&(t.delete(o),l.completionNotifier&&l.completionNotifier().catch(()=>{}),l.resolver());}function a(o){let l=t.get(o);l&&(t.delete(o),l.resolver());}return {register:r,complete:s,cancel:a}}function Y(t,e,r){return {async form(s,a){let o=a$2(a),l=await t.elicitInput({message:s,requestedSchema:o});return {action:l.action,content:l.content??{}}},async url(s,a,o){let l=o?.elicitationId??crypto.randomUUID(),c;o?.waitForCompletion&&e&&(c=e(l,t));let m=await t.elicitInput({mode:"url",message:s,url:a,elicitationId:l});if(m.action==="accept"&&c){let n=o?.timeout??3e5,v,y=new Promise((S,T)=>{v=setTimeout(()=>T(new Error("Elicitation timed out")),n);});try{await Promise.race([c,y]);}catch(S){throw r&&r(l),S}finally{clearTimeout(v);}}else c&&r&&r(l);return {action:m.action}}}}function J(t){return async()=>{try{return (await t.listRoots()).roots.map(r=>{let s={uri:r.uri};return r.name!==void 0&&(s.name=r.name),s})}catch{return []}}}function Z(t){async function e(s,a){let o={messages:[{role:"user",content:{type:"text",text:s}}],maxTokens:a?.maxTokens??1024};a?.model!==void 0&&(o.modelPreferences={hints:[{name:a.model}]}),a?.system!==void 0&&(o.systemPrompt=a.system),a?.temperature!==void 0&&(o.temperature=a.temperature),a?.stopSequences!==void 0&&(o.stopSequences=a.stopSequences);let c=(await t.createMessage(o)).content;return c.type==="text"?c.text:""}async function r(s){return t.createMessage(s)}return Object.assign(e,{raw:r})}function D(t,e,r,s,a,o,l,c,m){return {toolName:s,args:a,session:r,signal:o,sessionId:e,elicit:Y(t,c,m),roots:J(t),sample:Z(t),text:a$3,json:b$2,error:c$2,image:d$1,store:l,userStore:c$1(r,l)}}function k(t,e){let r=t.sessions.get(e);if(!r&&(r={data:new Map,grants:new Set,toolOverrides:new Map,resourceOverrides:new Map},t.sessions.set(e,r),t.onSessionCreate))try{Promise.resolve(t.onSessionCreate(e)).catch(()=>{});}catch{}return r}function q(t,e$1,r){let s=k(t,r);return e(e$1.hiddenByMiddlewares,e$1.name,s.toolOverrides,s.grants)}function b(t,e$1,r){let s=k(t,r);return e(e$1.hiddenByMiddlewares,e$1.uri,s.resourceOverrides,s.grants)}function E(t,e$1,r){let s=k(t,r);return e(e$1.hiddenByMiddlewares,e$1.name,s.toolOverrides,s.grants)}function U(t,e,r){(r&&t.serverBySession.get(r)||e).sendToolListChanged().catch(()=>{});}function $(t,e,r){(r&&t.serverBySession.get(r)||e).sendResourceListChanged().catch(()=>{});}function R(t,e,r){let s=k(t,r);return {get(a){return s.data.get(a)},set(a,o){s.data.set(a,o);},authorize(a){s.grants.add(a),U(t,e,r),$(t,e,r);},revoke(a){s.grants.delete(a),U(t,e,r),$(t,e,r);},enableTools(...a){for(let o of a)s.toolOverrides.set(o,"enabled");U(t,e,r);},disableTools(...a){for(let o of a)s.toolOverrides.set(o,"disabled");U(t,e,r);},enableResources(...a){for(let o of a)s.resourceOverrides.set(o,"enabled");$(t,e,r);},disableResources(...a){for(let o of a)s.resourceOverrides.set(o,"disabled");$(t,e,r);}}}function Q(){let t=new Set,e=new InMemoryTaskStore;return {taskStore:new Proxy(e,{get(s,a,o){return a==="updateTaskStatus"?async(l,c,...m)=>(c==="cancelled"&&t.add(l),s.updateTaskStatus.call(s,l,c,...m)):Reflect.get(s,a,o)}}),cancelledTaskIds:t}}function F(t,e,r,s,a){t.setRequestHandler(ListToolsRequestSchema,(o,l)=>{let c=l.sessionId??"default",m=[];for(let n of e.tools.values())q(e,n,c)&&m.push({name:n.name,description:n.description,inputSchema:a$2(n.input)});for(let n of e.tasks.values())E(e,n,c)&&m.push({name:n.name,description:n.description,inputSchema:a$2(n.input),execution:{taskSupport:"required"}});return {tools:m}}),t.setRequestHandler(CallToolRequestSchema,async(o,l)=>{let{name:c,arguments:m}=o.params,n=l.sessionId??"default",v=e.tools.get(c);if(v){if(!q(e,v,n))throw new McpError(ErrorCode.MethodNotFound,`Tool not available: ${c}`);let S=m??{},T=D(t,n,R(e,r,n),c,S,l.signal,e.store,(u,f)=>s.register(u,n,f),s.cancel),d=()=>Promise.resolve(v.handler(S,T));return k$1(v.middlewares,T,d)()}let y=e.tasks.get(c);if(y){if(!E(e,y,n))throw new McpError(ErrorCode.MethodNotFound,`Tool not available: ${c}`);let S=l.taskStore;if(!S)throw new McpError(ErrorCode.InternalError,"Task store not available");let T=await S.createTask({pollInterval:1e3}),d=T.taskId,i={progress(p,w){if(a.has(d))return;let P=w?`${p}% ${w}`:`${p}%`;S.updateTaskStatus(d,"working",P).catch(()=>{});},get cancelled(){return a.has(d)}},u=m??{},f={...D(t,n,R(e,r,n),c,u,l.signal,e.store,(p,w)=>s.register(p,n,w),s.cancel),task:i},g=async()=>{let p=(async()=>{try{let w=await y.handler(u,f);a.has(d)||await S.storeTaskResult(d,"completed",w);}catch(w){if(!a.has(d)){let P=w instanceof Error?w.message:String(w);await S.storeTaskResult(d,"failed",c$2(P)).catch(()=>{});}}})();return e.runningTasks.add(p),p.finally(()=>e.runningTasks.delete(p)),{task:T}};return k$1(y.middlewares,f,g)()}throw new McpError(ErrorCode.MethodNotFound,`Unknown tool: ${c}`)}),t.setRequestHandler(ListResourcesRequestSchema,(o,l)=>{let c=l.sessionId??"default",m=[];for(let n of e.resources.values())!n.isTemplate&&b(e,n,c)&&m.push({uri:n.uri,name:n.name,description:n.description,mimeType:n.mimeType});return {resources:m}}),t.setRequestHandler(ListResourceTemplatesRequestSchema,(o,l)=>{let c=l.sessionId??"default",m=[];for(let n of e.resources.values())n.isTemplate&&b(e,n,c)&&m.push({uriTemplate:n.uri,name:n.name,description:n.description,mimeType:n.mimeType});return {resourceTemplates:m}}),t.setRequestHandler(ReadResourceRequestSchema,async(o,l)=>{let{uri:c}=o.params,m=f(e.resources,c);if(!m)throw new McpError(ErrorCode.InvalidRequest,`Unknown resource: ${c}`);let n=l.sessionId??"default";if(!b(e,m,n))throw new McpError(ErrorCode.InvalidRequest,`Resource not available: ${c}`);let v=R(e,r,n),y={uri:c,session:v,sessionId:n,roots:J(t),store:e.store,userStore:c$1(v,e.store)},S=D(t,n,v,m.uri,{},l.signal,e.store,(i,u)=>s.register(i,n,u),s.cancel),T=async()=>{let i=await m.handler(c,y);return {contents:[{uri:c,mimeType:i.mimeType??m.mimeType,...i.text!=null?{text:i.text}:{},...i.blob!=null?{blob:i.blob}:{}}]}};return k$1(m.middlewares,S,T)()});}function X(t,e,r,s,a,o,l){let c=null;async function m(){return c||(c=(await import('@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js')).WebStandardStreamableHTTPServerTransport),c}let n={tools:{listChanged:true},resources:{listChanged:true},tasks:{list:{},cancel:{},requests:{tools:{call:{}}}}};function v(){let y=new Server(t,{capabilities:n,taskStore:r});return l(y,e,s,a,o),y}return function(S){if(S?.sessionless)return async i=>{let u=await m(),f=v(),g=new u({sessionIdGenerator:void 0,enableJsonResponse:S?.enableJsonResponse});return await f.connect(g),g.handleRequest(i)};let T=new Map,d=false;return async i=>{!d&&e.onServerStart&&(d=true,Promise.resolve(e.onServerStart()).catch(()=>{}));let u=await m(),f=i.headers.get("mcp-session-id");if(f){let p=T.get(f);return p?(S?.onRequest&&await S.onRequest(i,f,R(e,s,f)),p.transport.handleRequest(i)):new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Session not found"}}),{status:404,headers:{"Content-Type":"application/json"}})}let g=v(),h=new u({sessionIdGenerator:S?.sessionIdGenerator??(()=>crypto.randomUUID()),enableJsonResponse:S?.enableJsonResponse,onsessioninitialized:p=>{T.set(p,{server:g,transport:h}),e.serverBySession.set(p,g),S?.onRequest&&S.onRequest(i,p,R(e,s,p));},onsessionclosed:p=>{T.delete(p),e.serverBySession.delete(p);let w=e.sessions.get(p)?.data??new Map;e.sessions.delete(p),e.onSessionDestroy&&Promise.resolve(e.onSessionDestroy(p,w)).catch(()=>{});}});return await g.connect(h),g.onclose=()=>{for(let[p,w]of T)if(w.server===g){T.delete(p),e.serverBySession.delete(p);let P=e.sessions.get(p)?.data??new Map;e.sessions.delete(p),e.onSessionDestroy&&Promise.resolve(e.onSessionDestroy(p,P)).catch(()=>{});break}},h.handleRequest(i)}}}function ce(t){let e={store:t.store??a(),globalMiddlewares:[],tools:new Map,resources:new Map,tasks:new Map,sessions:new Map,serverBySession:new Map,onServerStart:t.onServerStart,onSessionCreate:t.onSessionCreate,onSessionDestroy:t.onSessionDestroy,runningTasks:new Set},r=K(),{taskStore:s,cancelledTaskIds:a$2}=Q(),o=new Server(t,{capabilities:{tools:{listChanged:true},resources:{listChanged:true},tasks:{list:{},cancel:{},requests:{tools:{call:{}}}}},taskStore:s});F(o,e,o,r,a$2),o.onclose=()=>{let d="default",i=e.sessions.get(d)?.data??new Map;if(e.sessions.delete(d),e.onSessionDestroy)try{Promise.resolve(e.onSessionDestroy(d,i)).catch(()=>{});}catch{}};function l(d){e.globalMiddlewares.push(d);}function c$1(...d){let i=d[0],u=b$1(`tool("${i}")`,d.slice(1));if(typeof u.config.name=="string")throw new TypeError(`tool("${i}"): second-to-last argument must be a config object`);let f=u.config,g=[...e.globalMiddlewares,...u.middlewares],h={name:i,description:f.description,middlewares:g};e.tools.set(i,{name:i,description:f.description,input:f.input,handler:u.handler,middlewares:g,hiddenByMiddlewares:c(h,g)});}function m(...d$1){let i=d$1[0],u=b$1(`resource("${i}")`,d$1.slice(1));if(typeof u.config.name!="string")throw new TypeError(`resource("${i}"): second-to-last argument must be a config object with a "name" property`);let f=u.config,g=[...e.globalMiddlewares,...u.middlewares],h={name:f.name,description:f.description,middlewares:g},p=i.includes("{");e.resources.set(i,{uri:i,isTemplate:p,uriPattern:p?d(i):null,name:f.name,description:f.description,mimeType:f.mimeType,handler:u.handler,middlewares:g,hiddenByMiddlewares:c(h,g)});}function n(...d){let i=d[0],u=b$1(`task("${i}")`,d.slice(1));if(typeof u.config.name=="string")throw new TypeError(`task("${i}"): second-to-last argument must be a config object`);let f=u.config,g=[...e.globalMiddlewares,...u.middlewares],h={name:i,description:f.description,middlewares:g};e.tasks.set(i,{name:i,description:f.description,input:f.input,handler:u.handler,middlewares:g,hiddenByMiddlewares:c(h,g)});}async function v(){let{StdioServerTransport:d}=await import('@modelcontextprotocol/sdk/server/stdio.js'),i=new d;await o.connect(i),e.onServerStart&&await Promise.resolve(e.onServerStart()).catch(()=>{});}let y=X(t,e,s,o,r,a$2,F);async function S(){await Promise.allSettled(e.runningTasks);}let T={use:l,tool:c$1,resource:m,task:n,stdio:v,http:y,drain:S,session:d=>R(e,o,d),completeElicitation:r.complete,store:e.store};return a$1(T,{server:o,getSession:d=>k(e,d),isToolVisible(d,i){let u=e.tools.get(d);return u?q(e,u,i):false},isResourceVisible(d,i){let u=e.resources.get(d);return u?b(e,u,i):false},isTaskVisible(d,i){let u=e.tasks.get(d);return u?E(e,u,i):false},createSessionAPI:d=>R(e,o,d)}),T}export{ce as createMCPServer};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { v as ToolMiddleware } from '../types-
|
|
1
|
+
import { v as ToolMiddleware } from '../types-CB6NartK.js';
|
|
2
2
|
import '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
interface CacheOptions {
|
|
6
6
|
/** TTL in seconds. */
|
|
7
7
|
ttl: number;
|
|
8
|
-
/** Custom cache key builder. Default: `cache:${toolName}:${
|
|
8
|
+
/** Custom cache key builder. Default: `cache:${toolName}:${stableStringify(args)}`. */
|
|
9
9
|
key?: (toolName: string, args: Record<string, unknown>) => string;
|
|
10
10
|
}
|
|
11
11
|
declare function cache(options: CacheOptions): ToolMiddleware;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function i(n){if(n===null||typeof n!="object")return JSON.stringify(n);if(Array.isArray(n))return `[${n.map(i).join(",")}]`;let r=n;return `{${Object.keys(r).sort().map(t=>`${JSON.stringify(t)}:${i(r[t])}`).join(",")}}`}function c(n){let{ttl:r}=n,e=n.key??((t,o)=>`cache:${t}:${i(o)}`);return {name:"cache",async onCall(t,o){let s=e(t.toolName,t.args),a=await t.store.get(s);return a||o()},async onResult(t,o){if(!t.isError){let s=e(o.toolName,o.args);await o.store.set(s,t,r);}return t}}}export{c as cache};
|
package/dist/middleware/jwt.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v as ToolMiddleware } from '../types-
|
|
1
|
+
import { v as ToolMiddleware } from '../types-CB6NartK.js';
|
|
2
2
|
import '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
@@ -9,11 +9,31 @@ interface RateLimitOptions {
|
|
|
9
9
|
windowMs?: number;
|
|
10
10
|
/** Error message. */
|
|
11
11
|
message?: string;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Use persistent Store for distributed rate limiting.
|
|
14
|
+
* Default: `false` (session-scoped — each session gets its own counter,
|
|
15
|
+
* so a client can bypass the limit by reconnecting).
|
|
16
|
+
* Set to `true` for production rate limiting across sessions.
|
|
17
|
+
*/
|
|
13
18
|
store?: boolean;
|
|
14
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Scope rate limiting per user (implies `store: true`).
|
|
21
|
+
* Default: `false`.
|
|
22
|
+
*
|
|
23
|
+
* **Warning:** Users without `session.set("user", ...)` all share a single
|
|
24
|
+
* `"anon"` bucket. Ensure the user is set in session before rate-limited calls
|
|
25
|
+
* if per-user isolation is required.
|
|
26
|
+
*/
|
|
15
27
|
perUser?: boolean;
|
|
16
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Rate limiting middleware.
|
|
31
|
+
*
|
|
32
|
+
* **Default behavior is session-scoped** — each MCP session gets its own counter.
|
|
33
|
+
* A client can bypass the limit by creating a new session (reconnecting).
|
|
34
|
+
* For production use, set `store: true` to share counters across sessions,
|
|
35
|
+
* or `perUser: true` to scope limits per authenticated user.
|
|
36
|
+
*/
|
|
17
37
|
declare function rateLimit(options: RateLimitOptions): ToolMiddleware;
|
|
18
38
|
|
|
19
39
|
export { type RateLimitOptions, rateLimit };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import {b}from'../chunk-
|
|
1
|
+
import {b}from'../chunk-YIQZZYBJ.mjs';import {c}from'../chunk-VAAZWX4U.mjs';function g(t){let{max:a,windowMs:o=6e4}=t,w=t.store===true||t.perUser===true,d=t.perUser===true,l=t.message??`Rate limit exceeded. Max ${a} calls per ${o/1e3}s.`,f=Math.ceil(o/1e3);return {name:"rateLimit",async onCall(e,n){let i=Date.now();if(w){let u=d?`rateLimit:${b(e.session)??"anon"}:${e.toolName}`:`rateLimit:${e.toolName}`,s=await e.store.get(u);return !s||i>=s.resetAt?(await e.store.set(u,{count:1,resetAt:i+o},f),n()):s.count>=a?c(l):(await e.store.set(u,{...s,count:s.count+1},f),n())}let m=`rateLimit:${e.toolName}`,r=e.session.get(m);return !r||i>=r.resetAt?(e.session.set(m,{count:1,resetAt:i+o}),n()):r.count>=a?c(l):(e.session.set(m,{...r,count:r.count+1}),n())}}}export{g as rateLimit};
|
package/dist/middleware/tip.d.ts
CHANGED
package/dist/store.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { n as Store, m as Session, x as UserStore } from './types-
|
|
1
|
+
import { n as Store, m as Session, x as UserStore } from './types-CB6NartK.js';
|
|
2
2
|
import '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface MemoryStoreOptions {
|
|
6
|
+
/** Maximum number of entries. When exceeded, expired entries are swept first, then least-recently-accessed entries are evicted. Default: 10000. */
|
|
7
|
+
maxEntries?: number;
|
|
8
|
+
}
|
|
9
|
+
declare function memoryStore(options?: MemoryStoreOptions): Store;
|
|
6
10
|
declare function resolveUserId(session: Session): string | undefined;
|
|
7
11
|
declare function createUserStore(session: Session, store: Store): UserStore;
|
|
8
12
|
|
|
9
|
-
export { createUserStore, memoryStore, resolveUserId };
|
|
13
|
+
export { type MemoryStoreOptions, createUserStore, memoryStore, resolveUserId };
|
package/dist/store.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{c as createUserStore,a as memoryStore,b as resolveUserId}from'./chunk-
|
|
1
|
+
export{c as createUserStore,a as memoryStore,b as resolveUserId}from'./chunk-YIQZZYBJ.mjs';
|
package/dist/test.d.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import { m as Session, M as MCPServer } from './types-
|
|
2
|
+
import { m as Session, M as MCPServer } from './types-CB6NartK.js';
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
4
|
import 'zod';
|
|
4
5
|
|
|
6
|
+
interface SessionState {
|
|
7
|
+
data: Map<string, unknown>;
|
|
8
|
+
grants: Set<string>;
|
|
9
|
+
toolOverrides: Map<string, "enabled" | "disabled">;
|
|
10
|
+
resourceOverrides: Map<string, "enabled" | "disabled">;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface InternalAccess {
|
|
14
|
+
server: Server;
|
|
15
|
+
getSession(sessionId: string): SessionState;
|
|
16
|
+
isToolVisible(toolName: string, sessionId: string): boolean;
|
|
17
|
+
isResourceVisible(uri: string, sessionId: string): boolean;
|
|
18
|
+
isTaskVisible(taskName: string, sessionId: string): boolean;
|
|
19
|
+
createSessionAPI(sessionId: string): Session;
|
|
20
|
+
}
|
|
21
|
+
declare function getInternals(server: MCPServer): InternalAccess;
|
|
22
|
+
|
|
5
23
|
interface TestClient {
|
|
6
24
|
/** List visible tool names. */
|
|
7
25
|
listTools(): Promise<string[]>;
|
|
@@ -36,4 +54,4 @@ declare const matchers: {
|
|
|
36
54
|
};
|
|
37
55
|
};
|
|
38
56
|
|
|
39
|
-
export { type TestClient, createTestClient, matchers };
|
|
57
|
+
export { type InternalAccess, type TestClient, createTestClient, getInternals, matchers };
|
package/dist/test.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
async function
|
|
1
|
+
import {b}from'./chunk-CI343GXC.mjs';export{b as getInternals}from'./chunk-CI343GXC.mjs';async function w(o){let{Client:r}=await import('@modelcontextprotocol/sdk/client/index.js'),{InMemoryTransport:u}=await import('@modelcontextprotocol/sdk/inMemory.js'),[n,a]=u.createLinkedPair(),t=new r({name:"lynq-test",version:"1.0.0"},{capabilities:{tasks:{}}}),m=b(o);await Promise.all([m.server.connect(a),t.connect(n)]);let l=m.createSessionAPI("default");return {async listTools(){return (await t.listTools()).tools.map(s=>s.name)},async callTool(e,s={}){return t.callTool({name:e,arguments:s})},async callToolText(e,s={}){let i=await t.callTool({name:e,arguments:s});if(i.isError){let g=i.content.find(T=>T.type==="text")?.text??"Unknown error";throw new Error(g)}return i.content.find(p=>p.type==="text")?.text??""},async listResources(){return (await t.listResources()).resources.map(s=>s.uri)},async listResourceTemplates(){return (await t.listResourceTemplates()).resourceTemplates.map(s=>s.uriTemplate)},async readResource(e){return (await t.readResource({uri:e})).contents[0]?.text??""},authorize(e){l.authorize(e);},revoke(e){l.revoke(e);},session:l,async close(){await t.close();}}}var R={toHaveTextContent(o,r){let n=o.content.filter(t=>t.type==="text").map(t=>t.text??""),a=n.some(t=>t.includes(r));return {pass:a,message:()=>a?`Expected result not to contain "${r}"`:`Expected result to contain "${r}", got: ${n.join(", ")}`}},toBeError(o){let r=o.isError===true;return {pass:r,message:()=>r?"Expected result not to be an error":"Expected result to be an error (isError: true)"}}};export{w as createTestClient,R as matchers};
|
|
@@ -38,8 +38,8 @@ interface ServerOptions extends ServerInfo {
|
|
|
38
38
|
onServerStart?: () => void | Promise<void>;
|
|
39
39
|
/** Called when a new session is created. */
|
|
40
40
|
onSessionCreate?: (sessionId: string) => void | Promise<void>;
|
|
41
|
-
/** Called when a session is destroyed (HTTP session close or transport disconnect). */
|
|
42
|
-
onSessionDestroy?: (sessionId: string) => void | Promise<void>;
|
|
41
|
+
/** Called when a session is destroyed (HTTP session close or transport disconnect). Session data is provided for cleanup (e.g. resolving user ID to delete store entries). */
|
|
42
|
+
onSessionDestroy?: (sessionId: string, data: ReadonlyMap<string, unknown>) => void | Promise<void>;
|
|
43
43
|
}
|
|
44
44
|
interface Session {
|
|
45
45
|
/** Get a session-scoped value. */
|
|
@@ -194,14 +194,29 @@ interface ResourceContext {
|
|
|
194
194
|
userStore: UserStore;
|
|
195
195
|
}
|
|
196
196
|
type ResourceHandler = (uri: string, c: ResourceContext) => ResourceContent | Promise<ResourceContent>;
|
|
197
|
+
/**
|
|
198
|
+
* Options for the HTTP adapter returned by `server.http()`.
|
|
199
|
+
*
|
|
200
|
+
* **Security note:** Session routing relies on the `Mcp-Session-Id` header.
|
|
201
|
+
* Session IDs are UUIDs (hard to guess) but can leak via logs or proxies.
|
|
202
|
+
* Use `onRequest` to add additional validation (e.g., Bearer token check,
|
|
203
|
+
* IP binding) or `sessionIdGenerator` to produce HMAC-signed session tokens.
|
|
204
|
+
*/
|
|
197
205
|
interface HttpAdapterOptions {
|
|
198
206
|
/** Disable session management. Default: false. */
|
|
199
207
|
sessionless?: boolean;
|
|
200
|
-
/**
|
|
208
|
+
/**
|
|
209
|
+
* Custom session ID generator. Default: `crypto.randomUUID()`.
|
|
210
|
+
* Can be used to produce HMAC-signed session tokens for additional security.
|
|
211
|
+
*/
|
|
201
212
|
sessionIdGenerator?: () => string;
|
|
202
213
|
/** Return JSON instead of SSE streams. Default: false. */
|
|
203
214
|
enableJsonResponse?: boolean;
|
|
204
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* Called on each HTTP request after session is resolved.
|
|
217
|
+
* Use to inject HTTP headers (e.g., Bearer tokens) into MCP sessions,
|
|
218
|
+
* or to validate that the requester owns the session (e.g., IP binding, token check).
|
|
219
|
+
*/
|
|
205
220
|
onRequest?: (req: Request, sessionId: string, session: Session) => void | Promise<void>;
|
|
206
221
|
}
|
|
207
222
|
interface MCPServer {
|
|
@@ -229,6 +244,8 @@ interface MCPServer {
|
|
|
229
244
|
completeElicitation(elicitationId: string): void;
|
|
230
245
|
/** The persistent store instance. */
|
|
231
246
|
store: Store;
|
|
247
|
+
/** @experimental Wait for all running tasks to settle. For graceful shutdown. */
|
|
248
|
+
drain(): Promise<void>;
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
export { json as A, text as B, type Elicit as E, type HttpAdapterOptions as H, type MCPServer as M, type ResourceConfig as R, type ServerOptions as S, type TaskConfig as T, type User as U, type ElicitFormResult as a, type ElicitUrlOptions as b, type ElicitUrlResult as c, type ResourceContent as d, type ResourceContext as e, type ResourceHandler as f, type RootInfo as g, type Sample as h, type SampleOptions as i, type SampleRawParams as j, type SampleRawResult as k, type ServerInfo as l, type Session as m, type Store as n, type TaskContext as o, type TaskControl as p, type TaskHandler as q, type ToolConfig as r, type ToolContext as s, type ToolHandler as t, type ToolInfo as u, type ToolMiddleware as v, type ToolResponse as w, type UserStore as x, error as y, image as z };
|
package/package.json
CHANGED
package/dist/chunk-3BJEUP3F.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import {createHmac,timingSafeEqual}from'crypto';import {normalizeObjectSchema}from'@modelcontextprotocol/sdk/server/zod-compat.js';import {toJsonSchemaCompat}from'@modelcontextprotocol/sdk/server/zod-json-schema-compat.js';function h(e){if(e==null)return {type:"object"};let t=normalizeObjectSchema(e);return t?toJsonSchemaCompat(t):e}function T(e,t){let o=t[t.length-1];if(typeof o!="function")throw new TypeError(`${e}: last argument must be a handler function`);let n=t[t.length-2];if(n==null||typeof n!="object"||Array.isArray(n))throw new TypeError(`${e}: second-to-last argument must be a config object`);let r=t.slice(0,-2);for(let s of r)if(!s||typeof s!="object"||typeof s.name!="string")throw new TypeError(`${e}: each middleware must have a "name" property`);return {middlewares:r,config:n,handler:o}}function R(e,t){let o=[];for(let n of t)n.onRegister?.(e)===false&&o.push(n.name);return o}function x(e){let o=e.split(/\{[^}]+\}/).map(n=>n.replace(/[.*+?^$|()[\]\\]/g,"\\$&"));return new RegExp(`^${o.join("(.+)")}$`)}function y(e,t,o,n){let r=o.get(t);if(r==="disabled")return false;if(r==="enabled")return true;for(let s of e)if(!n.has(s))return false;return true}function b(e,t){let o=e.get(t);if(o)return o;for(let n of e.values())if(n.isTemplate&&n.uriPattern?.test(t))return n}function $(e,t,o){let n=`${e}:${t}`,r=createHmac("sha256",o).update(n).digest("hex");return `${n}:${r}`}function C(e,t){let o=e.split(":");if(o.length!==3)return null;let[n,r,s]=o,i=createHmac("sha256",t).update(`${n}:${r}`).digest("hex");try{if(!timingSafeEqual(Buffer.from(s,"hex"),Buffer.from(i,"hex")))return null}catch{return null}return {sessionId:n,elicitationId:r}}function M(e,t){if(!e)return false;let o=e.replace(/:\d+$/,"");return t.includes(o)}var S=["localhost","127.0.0.1","::1"];function j(e,t,o){let n=e.filter(l=>l.onCall),r=e.filter(l=>l.onResult).reverse(),s=0,i=async()=>{if(s>=n.length){let a=await o();for(let c of r)a=await c.onResult(a,t);return a}return n[s++].onCall(t,i)};return i}export{h as a,T as b,R as c,x as d,y as e,b as f,$ as g,C as h,M as i,S as j,j as k};
|
package/dist/chunk-63JN2KYH.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function d(){let r=new Map;return {async get(e){let n=r.get(e);if(n){if(n.expiresAt!==void 0&&Date.now()>n.expiresAt){r.delete(e);return}return n.value}},async set(e,n,t){if(r.size>=1e3){let s=Date.now();for(let[i,o]of r)o.expiresAt!==void 0&&s>o.expiresAt&&r.delete(i);}r.set(e,{value:n,expiresAt:t!==void 0?Date.now()+t*1e3:void 0});},async delete(e){r.delete(e);}}}function u(r){let e=r.get("user");if(e){if(typeof e=="string")return e;if(typeof e=="object"&&e!==null){let n=e;if(typeof n.id=="string")return n.id;if(typeof n.id=="number")return String(n.id);if(typeof n.sub=="string")return n.sub}}}function f(r,e){let n=()=>{let t=u(r);if(!t){let s=r.get("user");if(s){let i=typeof s=="object"?JSON.stringify(s):typeof s;throw new Error(`userStore: session has a "user" but could not resolve an ID. Expected: string | { id: string | number } | { sub: string }. Got: ${i}`)}throw new Error("userStore requires a user in session. Call session.set('user', ...) first.")}return t};return {async get(t){return e.get(`user:${n()}:${t}`)},async set(t,s,i){await e.set(`user:${n()}:${t}`,s,i);},async delete(t){await e.delete(`user:${n()}:${t}`);}}}export{d as a,u as b,f as c};
|