@frontmcp/skills 1.1.2-beta.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/catalog/TEMPLATE.md +16 -11
- package/catalog/frontmcp-authorities/SKILL.md +116 -11
- package/catalog/frontmcp-authorities/references/authority-profiles.md +39 -36
- package/catalog/frontmcp-authorities/references/claims-mapping.md +7 -0
- package/catalog/frontmcp-authorities/references/custom-evaluators.md +63 -14
- package/catalog/frontmcp-channels/SKILL.md +36 -0
- package/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md +8 -2
- package/catalog/frontmcp-channels/examples/channel-sources/replay-buffer.md +111 -30
- package/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md +45 -3
- package/catalog/frontmcp-channels/references/channel-sources.md +11 -3
- package/catalog/frontmcp-channels/references/channel-two-way.md +60 -89
- package/catalog/frontmcp-config/SKILL.md +111 -8
- package/catalog/frontmcp-config/examples/configure-auth-modes/local-self-signed-tokens.md +4 -4
- package/catalog/frontmcp-config/examples/configure-auth-modes/remote-enterprise-oauth.md +7 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/json-schema-ide-support.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/multi-target-with-security.md +12 -9
- package/catalog/frontmcp-config/examples/configure-http/cors-restricted-origins.md +2 -2
- package/catalog/frontmcp-config/examples/configure-http/entry-path-reverse-proxy.md +1 -1
- package/catalog/frontmcp-config/examples/configure-security-headers/csp-report-only.md +1 -1
- package/catalog/frontmcp-config/examples/configure-security-headers/full-production-headers.md +1 -1
- package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-basic.md +76 -0
- package/catalog/frontmcp-config/examples/configure-skills-http/audit-log-redis.md +116 -0
- package/catalog/frontmcp-config/examples/configure-skills-http/inject-instructions.md +59 -0
- package/catalog/frontmcp-config/references/configure-auth-modes.md +5 -5
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +27 -24
- package/catalog/frontmcp-config/references/configure-http.md +14 -10
- package/catalog/frontmcp-config/references/configure-security-headers.md +2 -2
- package/catalog/frontmcp-config/references/configure-session.md +25 -25
- package/catalog/frontmcp-config/references/configure-skills-http.md +157 -0
- package/catalog/frontmcp-config/references/configure-throttle.md +1 -1
- package/catalog/frontmcp-config/references/configure-transport.md +2 -2
- package/catalog/frontmcp-deployment/SKILL.md +112 -9
- package/catalog/frontmcp-deployment/examples/build-for-browser/browser-build-with-custom-entry.md +23 -11
- package/catalog/frontmcp-deployment/examples/build-for-browser/browser-crypto-and-storage.md +44 -17
- package/catalog/frontmcp-deployment/examples/build-for-browser/react-provider-setup.md +53 -21
- package/catalog/frontmcp-deployment/examples/build-for-cli/cli-binary-build.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-cli/unix-socket-daemon.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-mcpb/mcpb-bundle-build.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-sdk/connect-openai.md +1 -1
- package/catalog/frontmcp-deployment/examples/build-for-sdk/multi-platform-connect.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/basic-worker-deploy.md +7 -8
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +8 -6
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-with-kv-storage.md +5 -4
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/cdk-deployment.md +8 -5
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/lambda-handler-with-cors.md +20 -18
- package/catalog/frontmcp-deployment/examples/deploy-to-lambda/sam-template-basic.md +8 -5
- package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +3 -3
- package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-node/resource-limits.md +2 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/basic-multistage-dockerfile.md +2 -2
- package/catalog/frontmcp-deployment/examples/deploy-to-node-dockerfile/secure-nonroot-dockerfile.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-mcp-endpoint-test.md +23 -21
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +25 -22
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-skills-cache.md +23 -30
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/minimal-vercel-config.md +52 -28
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel-config/vercel-config-with-security-headers.md +32 -55
- package/catalog/frontmcp-deployment/examples/mcp-client-integration/http-remote.md +9 -0
- package/catalog/frontmcp-deployment/references/build-for-browser.md +40 -17
- package/catalog/frontmcp-deployment/references/build-for-cli.md +8 -8
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +43 -24
- package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +36 -25
- package/catalog/frontmcp-deployment/references/deploy-to-node-dockerfile.md +56 -14
- package/catalog/frontmcp-deployment/references/deploy-to-node.md +9 -6
- package/catalog/frontmcp-deployment/references/deploy-to-vercel-config.md +57 -58
- package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +49 -59
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +2 -0
- package/catalog/frontmcp-development/SKILL.md +186 -11
- package/catalog/frontmcp-development/examples/create-agent/custom-multi-pass-agent.md +1 -1
- package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +30 -27
- package/catalog/frontmcp-development/examples/create-job/job-with-permissions.md +13 -8
- package/catalog/frontmcp-development/examples/create-provider/basic-database-provider.md +33 -23
- package/catalog/frontmcp-development/examples/create-provider/config-and-api-providers.md +19 -10
- package/catalog/frontmcp-development/examples/create-tool/tool-with-rate-limiting-and-progress.md +3 -3
- package/catalog/frontmcp-development/examples/create-workflow/webhook-triggered-workflow.md +6 -4
- package/catalog/frontmcp-development/examples/decorators-guide/agent-skill-job-workflow.md +1 -1
- package/catalog/frontmcp-development/examples/decorators-guide/basic-server-with-app-and-tools.md +13 -8
- package/catalog/frontmcp-development/examples/decorators-guide/multi-app-with-plugins-and-providers.md +50 -23
- package/catalog/frontmcp-development/references/create-agent.md +47 -30
- package/catalog/frontmcp-development/references/create-job.md +69 -54
- package/catalog/frontmcp-development/references/create-plugin-hooks.md +45 -28
- package/catalog/frontmcp-development/references/create-plugin.md +10 -8
- package/catalog/frontmcp-development/references/create-prompt.md +3 -3
- package/catalog/frontmcp-development/references/create-provider.md +91 -51
- package/catalog/frontmcp-development/references/create-resource.md +3 -3
- package/catalog/frontmcp-development/references/create-skill.md +2 -2
- package/catalog/frontmcp-development/references/create-tool.md +7 -7
- package/catalog/frontmcp-development/references/create-workflow.md +8 -10
- package/catalog/frontmcp-development/references/decorators-guide.md +92 -56
- package/catalog/frontmcp-development/references/official-plugins.md +4 -3
- package/catalog/frontmcp-development/references/openapi-adapter.md +1 -1
- package/catalog/frontmcp-extensibility/SKILL.md +70 -10
- package/catalog/frontmcp-extensibility/examples/skill-audit-log/custom-store.md +197 -0
- package/catalog/frontmcp-extensibility/examples/skill-audit-log/verify-chain.md +68 -0
- package/catalog/frontmcp-extensibility/examples/vectoriadb/product-catalog-search.md +3 -5
- package/catalog/frontmcp-extensibility/examples/vectoriadb/semantic-search-with-persistence.md +4 -11
- package/catalog/frontmcp-extensibility/examples/vectoriadb/tfidf-keyword-search.md +41 -30
- package/catalog/frontmcp-extensibility/references/skill-audit-log.md +233 -0
- package/catalog/frontmcp-extensibility/references/vectoriadb.md +73 -63
- package/catalog/frontmcp-guides/SKILL.md +84 -27
- package/catalog/frontmcp-guides/examples/example-knowledge-base/agent-and-plugin.md +72 -62
- package/catalog/frontmcp-guides/examples/example-knowledge-base/vector-search-and-resources.md +32 -43
- package/catalog/frontmcp-guides/examples/example-task-manager/auth-and-crud-tools.md +24 -17
- package/catalog/frontmcp-guides/examples/example-task-manager/authenticated-e2e-tests.md +23 -21
- package/catalog/frontmcp-guides/examples/example-task-manager/redis-provider-with-di.md +47 -39
- package/catalog/frontmcp-guides/examples/example-weather-api/server-and-app-setup.md +16 -6
- package/catalog/frontmcp-guides/examples/example-weather-api/unit-and-e2e-tests.md +9 -8
- package/catalog/frontmcp-guides/references/example-knowledge-base.md +192 -265
- package/catalog/frontmcp-guides/references/example-task-manager.md +60 -54
- package/catalog/frontmcp-guides/references/example-weather-api.md +22 -24
- package/catalog/frontmcp-observability/SKILL.md +66 -2
- package/catalog/frontmcp-observability/examples/telemetry-api/skill-counters.md +100 -0
- package/catalog/frontmcp-observability/examples/tracing-setup/production-tracing.md +7 -2
- package/catalog/frontmcp-observability/examples/vendor-integrations/coralogix-setup.md +6 -2
- package/catalog/frontmcp-observability/references/telemetry-api.md +72 -8
- package/catalog/frontmcp-observability/references/testing-observability.md +33 -49
- package/catalog/frontmcp-observability/references/tracing-setup.md +12 -5
- package/catalog/frontmcp-observability/references/vendor-integrations.md +46 -1
- package/catalog/frontmcp-production-readiness/SKILL.md +134 -3
- package/catalog/frontmcp-production-readiness/examples/common-checklist/caching-and-performance.md +57 -36
- package/catalog/frontmcp-production-readiness/examples/common-checklist/observability-setup.md +1 -1
- package/catalog/frontmcp-production-readiness/examples/common-checklist/security-hardening.md +102 -6
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/daemon-socket-config.md +2 -1
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/graceful-shutdown-cleanup.md +66 -58
- package/catalog/frontmcp-production-readiness/examples/production-cli-daemon/security-and-permissions.md +5 -3
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/durable-objects-state.md +2 -1
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/wrangler-config.md +55 -76
- package/catalog/frontmcp-production-readiness/examples/production-lambda/cold-start-connection-reuse.md +43 -40
- package/catalog/frontmcp-production-readiness/examples/production-lambda/sam-template.md +63 -94
- package/catalog/frontmcp-production-readiness/examples/production-lambda/scaling-and-monitoring.md +28 -18
- package/catalog/frontmcp-production-readiness/examples/production-node-sdk/multi-instance-cleanup.md +29 -14
- package/catalog/frontmcp-production-readiness/examples/production-node-server/graceful-shutdown.md +58 -42
- package/catalog/frontmcp-production-readiness/examples/production-node-server/redis-session-scaling.md +5 -2
- package/catalog/frontmcp-production-readiness/examples/production-vercel/cold-start-optimization.md +41 -24
- package/catalog/frontmcp-production-readiness/examples/production-vercel/vercel-edge-config.md +56 -65
- package/catalog/frontmcp-production-readiness/references/common-checklist.md +17 -5
- package/catalog/frontmcp-production-readiness/references/production-cli-daemon.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-cloudflare.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-lambda.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-node-sdk.md +5 -5
- package/catalog/frontmcp-production-readiness/references/production-node-server.md +1 -1
- package/catalog/frontmcp-production-readiness/references/production-vercel.md +5 -5
- package/catalog/frontmcp-setup/SKILL.md +88 -0
- package/catalog/frontmcp-setup/examples/project-structure-nx/nx-workspace-with-apps.md +10 -4
- package/catalog/frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md +21 -8
- package/catalog/frontmcp-setup/examples/readme-guide/node-server-readme.md +3 -3
- package/catalog/frontmcp-setup/references/multi-app-composition.md +4 -3
- package/catalog/frontmcp-setup/references/project-structure-nx.md +15 -6
- package/catalog/frontmcp-setup/references/project-structure-standalone.md +18 -15
- package/catalog/frontmcp-setup/references/readme-guide.md +1 -1
- package/catalog/frontmcp-setup/references/setup-project.md +19 -5
- package/catalog/frontmcp-setup/references/setup-redis.md +27 -39
- package/catalog/frontmcp-setup/references/setup-sqlite.md +25 -18
- package/catalog/frontmcp-testing/SKILL.md +102 -15
- package/catalog/frontmcp-testing/examples/setup-testing/unit-test-tool-resource-prompt.md +3 -3
- package/catalog/frontmcp-testing/examples/test-auth/oauth-flow-test.md +50 -39
- package/catalog/frontmcp-testing/examples/test-auth/role-based-access-test.md +52 -29
- package/catalog/frontmcp-testing/examples/test-auth/token-factory-test.md +37 -20
- package/catalog/frontmcp-testing/examples/test-direct-client/basic-create-test.md +25 -15
- package/catalog/frontmcp-testing/examples/test-direct-client/openai-claude-format-test.md +27 -21
- package/catalog/frontmcp-testing/examples/test-e2e-handler/basic-e2e-test.md +29 -20
- package/catalog/frontmcp-testing/examples/test-e2e-handler/manual-client-with-transport.md +5 -3
- package/catalog/frontmcp-testing/examples/test-e2e-handler/tool-call-and-error-e2e.md +35 -26
- package/catalog/frontmcp-testing/examples/test-tool-unit/basic-tool-test.md +8 -3
- package/catalog/frontmcp-testing/examples/test-tool-unit/schema-validation-test.md +4 -1
- package/catalog/frontmcp-testing/examples/test-tool-unit/tool-error-handling-test.md +6 -3
- package/catalog/frontmcp-testing/references/setup-testing.md +35 -39
- package/catalog/frontmcp-testing/references/test-auth.md +86 -43
- package/catalog/frontmcp-testing/references/test-browser-build.md +1 -1
- package/catalog/frontmcp-testing/references/test-direct-client.md +29 -19
- package/catalog/frontmcp-testing/references/test-e2e-handler.md +31 -19
- package/catalog/frontmcp-testing/references/test-tool-unit.md +6 -2
- package/catalog/skills-manifest.json +428 -339
- package/package.json +1 -1
- package/src/manifest.d.ts +13 -0
- package/src/manifest.js.map +1 -1
package/catalog/frontmcp-production-readiness/examples/production-cloudflare/wrangler-config.md
CHANGED
|
@@ -2,88 +2,67 @@
|
|
|
2
2
|
name: wrangler-config
|
|
3
3
|
reference: production-cloudflare
|
|
4
4
|
level: basic
|
|
5
|
-
description: '
|
|
6
|
-
tags:
|
|
5
|
+
description: 'Checklist for verifying the `wrangler.toml` produced by `frontmcp build --target cloudflare` is production-ready. **Note:** configuration authoring lives in `frontmcp-deployment → references/deploy-to-cloudflare.md`; this file is checklist-only.'
|
|
6
|
+
tags:
|
|
7
|
+
- production
|
|
8
|
+
- cloudflare
|
|
9
|
+
- cache
|
|
10
|
+
- session
|
|
11
|
+
- wrangler
|
|
12
|
+
- checklist
|
|
7
13
|
features:
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
14
|
+
- Verify `main = "dist/cloudflare/index.js"` (the build adapter writes this — never override)
|
|
15
|
+
- Verify KV bindings for sessions and cache exist
|
|
16
|
+
- Verify staging / production environment configs are separated
|
|
17
|
+
- Verify secrets are NOT in `wrangler.toml` — use `wrangler secret put`
|
|
12
18
|
---
|
|
13
19
|
|
|
14
|
-
# Wrangler Configuration
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
[
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
[
|
|
49
|
-
|
|
50
|
-
routes = [
|
|
51
|
-
{ pattern = "mcp.example.com/*", zone_name = "example.com" }
|
|
52
|
-
]
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
// src/main.ts
|
|
57
|
-
import { FrontMcp } from '@frontmcp/sdk';
|
|
58
|
-
import { MyApp } from './my.app';
|
|
59
|
-
|
|
60
|
-
@FrontMcp({
|
|
61
|
-
info: { name: 'cf-mcp', version: '1.0.0' },
|
|
62
|
-
apps: [MyApp],
|
|
63
|
-
|
|
64
|
-
// CORS: use the workers.dev or custom domain
|
|
65
|
-
cors: {
|
|
66
|
-
origin: ['https://app.example.com'],
|
|
67
|
-
},
|
|
68
|
-
})
|
|
69
|
-
export default class CloudflareMcpServer {}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// src/worker.ts — Cloudflare Worker entry point
|
|
74
|
-
import { createCloudflareHandler } from '@frontmcp/adapters/cloudflare';
|
|
75
|
-
import Server from './main';
|
|
76
|
-
|
|
77
|
-
export default createCloudflareHandler(Server);
|
|
78
|
-
```
|
|
20
|
+
# Wrangler Configuration: Production-Readiness Checklist
|
|
21
|
+
|
|
22
|
+
Checklist for verifying the `wrangler.toml` produced by `frontmcp build --target cloudflare` is production-ready. **Note:** configuration authoring lives in `frontmcp-deployment → references/deploy-to-cloudflare.md`; this file is checklist-only.
|
|
23
|
+
|
|
24
|
+
## Build artifact checks
|
|
25
|
+
|
|
26
|
+
- [ ] `frontmcp build --target cloudflare` runs cleanly with no warnings
|
|
27
|
+
- [ ] The build adapter wrote `main = "dist/cloudflare/index.js"` to `wrangler.toml` — **do not hand-edit this line**; the adapter overwrites it on every build (PR #374 — overriding silently breaks `wrangler deploy`)
|
|
28
|
+
- [ ] `dist/cloudflare/index.js` exists after build and is what `wrangler deploy` uploads
|
|
29
|
+
- [ ] No hand-written `src/worker.ts` with a fictional `createCloudflareHandler(...)` — the build emits the entry; your code stays the decorated `@FrontMcp` class
|
|
30
|
+
- [ ] Bundle size is < 10 MB compressed (Workers limit)
|
|
31
|
+
|
|
32
|
+
## Routes & domains
|
|
33
|
+
|
|
34
|
+
- [ ] `routes = [{ pattern = "<host>/*", zone_name = "<zone>" }]` set for the right hostnames
|
|
35
|
+
- [ ] Custom domain or `*.workers.dev` subdomain matches what `cors.origin` accepts in your `@FrontMcp` config
|
|
36
|
+
- [ ] Separate `[env.staging]` and `[env.production]` blocks with distinct names + routes
|
|
37
|
+
|
|
38
|
+
## KV / Durable Objects / R2
|
|
39
|
+
|
|
40
|
+
- [ ] At least one `[[kv_namespaces]]` block bound for sessions if HA / multi-region (e.g. `binding = "MCP_SESSIONS"`)
|
|
41
|
+
- [ ] Cache binding (KV or Cache API) exists if `CachePlugin` is configured
|
|
42
|
+
- [ ] Durable Objects only used when stateful coordination is required (rate limiting, locks)
|
|
43
|
+
- [ ] R2 binding exists if the server reads/writes blobs (no filesystem in Workers)
|
|
44
|
+
|
|
45
|
+
## Secrets & environment
|
|
46
|
+
|
|
47
|
+
- [ ] No secrets in `wrangler.toml` — all set via `wrangler secret put <NAME>`
|
|
48
|
+
- [ ] `[vars]` block contains only non-sensitive config (region names, feature flags)
|
|
49
|
+
- [ ] `compatibility_date` set to a recent date and locked
|
|
50
|
+
|
|
51
|
+
## Deploy & observability
|
|
52
|
+
|
|
53
|
+
- [ ] Deploy with `wrangler deploy --env production` (not the unscoped form)
|
|
54
|
+
- [ ] `wrangler tail` works against the production worker for live debugging
|
|
55
|
+
- [ ] Dashboard shows recent successful deploys
|
|
79
56
|
|
|
80
57
|
## What This Demonstrates
|
|
81
58
|
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
59
|
+
- Verify `main = "dist/cloudflare/index.js"` (the build adapter writes this — never override)
|
|
60
|
+
- Verify KV bindings for sessions and cache exist
|
|
61
|
+
- Verify staging / production environment configs are separated
|
|
62
|
+
- Verify secrets are NOT in `wrangler.toml` — use `wrangler secret put`
|
|
86
63
|
|
|
87
64
|
## Related
|
|
88
65
|
|
|
89
|
-
-
|
|
66
|
+
- Configuration source of truth: `frontmcp-deployment/references/deploy-to-cloudflare.md`
|
|
67
|
+
- Build adapter source: `libs/cli/src/commands/build/adapters/cloudflare.ts`
|
|
68
|
+
- See `production-cloudflare` for the Cloudflare runtime / scaling checklist
|
|
@@ -2,18 +2,26 @@
|
|
|
2
2
|
name: cold-start-connection-reuse
|
|
3
3
|
reference: production-lambda
|
|
4
4
|
level: intermediate
|
|
5
|
-
description:
|
|
6
|
-
tags:
|
|
5
|
+
description: Shows how to minimize Lambda cold starts with lazy initialization on first call and the module-scope connection-reuse pattern for external services.
|
|
6
|
+
tags:
|
|
7
|
+
- production
|
|
8
|
+
- lambda
|
|
9
|
+
- performance
|
|
10
|
+
- cold
|
|
11
|
+
- start
|
|
12
|
+
- connection
|
|
7
13
|
features:
|
|
8
|
-
- 'Connection reuse pattern: caching
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
14
|
+
- 'Connection reuse pattern: caching the connection promise in module scope so it survives Lambda freeze/thaw'
|
|
15
|
+
- Lazy-loading heavy dependencies (`pg`) via dynamic `import()` on first use, not at module load
|
|
16
|
+
- Not closing connections on shutdown for Lambda (they survive freeze/thaw — and providers have no `onDestroy` hook anyway)
|
|
17
|
+
- Keeping module scope lightweight with no heavy initialization
|
|
12
18
|
---
|
|
13
19
|
|
|
14
20
|
# Cold Start Optimization and Connection Reuse
|
|
15
21
|
|
|
16
|
-
Shows how to minimize Lambda cold starts with lazy initialization
|
|
22
|
+
Shows how to minimize Lambda cold starts with lazy initialization on first call and the module-scope connection-reuse pattern for external services.
|
|
23
|
+
|
|
24
|
+
> `@Provider`-decorated classes do NOT have `onInit` / `onDestroy` lifecycle hooks. Initialize lazily on first method call. For Lambda specifically, do **not** wire any shutdown hook for DB connections — Lambda freezes the execution context between invocations and your connection survives, so explicit close is wrong.
|
|
17
25
|
|
|
18
26
|
## Code
|
|
19
27
|
|
|
@@ -23,38 +31,34 @@ import { Provider, ProviderScope } from '@frontmcp/sdk';
|
|
|
23
31
|
|
|
24
32
|
export const DB_CLIENT = Symbol('DbClient');
|
|
25
33
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
let cachedConnection: unknown | undefined;
|
|
34
|
+
// Module-scope cache — survives freeze/thaw between Lambda invocations.
|
|
35
|
+
let cachedConnectionPromise: Promise<unknown> | undefined;
|
|
29
36
|
|
|
30
37
|
@Provider({ token: DB_CLIENT, scope: ProviderScope.GLOBAL })
|
|
31
38
|
export class DbConnectionProvider {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
// Lazy on first getConnection() — heavy SDK import does not run at module load.
|
|
40
|
+
async getConnection(): Promise<unknown> {
|
|
41
|
+
if (!cachedConnectionPromise) {
|
|
42
|
+
const promise = (async () => {
|
|
43
|
+
const { Client } = await import('pg');
|
|
44
|
+
const client = new Client({
|
|
45
|
+
host: process.env.DB_HOST,
|
|
46
|
+
connectionTimeoutMillis: 5000, // Don't hang on connection attempts
|
|
47
|
+
});
|
|
48
|
+
await client.connect();
|
|
49
|
+
return client;
|
|
50
|
+
})();
|
|
51
|
+
// Reset on failure so the next warm invocation can retry instead of
|
|
52
|
+
// permanently caching a rejected promise.
|
|
53
|
+
promise.catch(() => {
|
|
54
|
+
if (cachedConnectionPromise === promise) cachedConnectionPromise = undefined;
|
|
55
|
+
});
|
|
56
|
+
cachedConnectionPromise = promise;
|
|
39
57
|
}
|
|
40
|
-
|
|
41
|
-
// Lazy-load the database driver — reduces cold start time
|
|
42
|
-
const { Client } = await import('pg');
|
|
43
|
-
this.connection = new Client({
|
|
44
|
-
host: process.env.DB_HOST,
|
|
45
|
-
connectionTimeoutMillis: 5000, // Don't hang on connection attempts
|
|
46
|
-
});
|
|
47
|
-
await (this.connection as { connect: () => Promise<void> }).connect();
|
|
48
|
-
|
|
49
|
-
// Cache for warm invocations
|
|
50
|
-
cachedConnection = this.connection;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
getConnection() {
|
|
54
|
-
return this.connection;
|
|
58
|
+
return cachedConnectionPromise;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
// Note:
|
|
61
|
+
// Note: Do NOT close in any shutdown hook for Lambda — connection survives freeze/thaw.
|
|
58
62
|
}
|
|
59
63
|
```
|
|
60
64
|
|
|
@@ -77,12 +81,11 @@ import { DB_CLIENT } from '../providers/db-connection.provider';
|
|
|
77
81
|
})
|
|
78
82
|
export class OptimizedQueryTool extends ToolContext {
|
|
79
83
|
async execute(input: { id: string }) {
|
|
80
|
-
const db = this.get(DB_CLIENT);
|
|
84
|
+
const db = this.get(DB_CLIENT) as { getConnection: () => Promise<{ query: Function }> };
|
|
81
85
|
|
|
82
86
|
// Parameterized query — prevents SQL injection
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
.query('SELECT * FROM records WHERE id = $1', [input.id]);
|
|
87
|
+
const conn = await db.getConnection();
|
|
88
|
+
const result = await conn.query('SELECT * FROM records WHERE id = $1', [input.id]);
|
|
86
89
|
|
|
87
90
|
if (!result.rows[0]) {
|
|
88
91
|
this.fail(new Error(`Record not found: ${input.id}`));
|
|
@@ -113,9 +116,9 @@ export default class FastLambdaServer {}
|
|
|
113
116
|
|
|
114
117
|
## What This Demonstrates
|
|
115
118
|
|
|
116
|
-
- Connection reuse pattern: caching
|
|
117
|
-
- Lazy-loading heavy dependencies (`pg`) via dynamic `import()`
|
|
118
|
-
- Not closing connections
|
|
119
|
+
- Connection reuse pattern: caching the connection promise in module scope so it survives Lambda freeze/thaw
|
|
120
|
+
- Lazy-loading heavy dependencies (`pg`) via dynamic `import()` on first use, not at module load
|
|
121
|
+
- Not closing connections on shutdown for Lambda (they survive freeze/thaw — and providers have no `onDestroy` hook anyway)
|
|
119
122
|
- Keeping module scope lightweight with no heavy initialization
|
|
120
123
|
|
|
121
124
|
## Related
|
|
@@ -2,106 +2,75 @@
|
|
|
2
2
|
name: sam-template
|
|
3
3
|
reference: production-lambda
|
|
4
4
|
level: basic
|
|
5
|
-
description: '
|
|
6
|
-
tags:
|
|
5
|
+
description: 'Checklist for verifying the SAM template pairs correctly with the bundle produced by `frontmcp build --target lambda`. **Note:** configuration authoring lives in `frontmcp-deployment → references/deploy-to-lambda.md`; this file is checklist-only.'
|
|
6
|
+
tags:
|
|
7
|
+
- production
|
|
8
|
+
- lambda
|
|
9
|
+
- session
|
|
10
|
+
- sam
|
|
11
|
+
- checklist
|
|
7
12
|
features:
|
|
8
|
-
- '
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
+
- 'Verify `Handler: handler.handler` with `CodeUri: dist/lambda/` (the build emits `dist/lambda/handler.cjs`)'
|
|
14
|
+
- No hand-written `src/lambda.ts` with a fictional `createLambdaHandler` import
|
|
15
|
+
- DynamoDB session table has TTL enabled for automatic cleanup
|
|
16
|
+
- IAM policies are scoped (no `*` resources / actions)
|
|
17
|
+
- API Gateway proxy route forwards to the function
|
|
13
18
|
---
|
|
14
19
|
|
|
15
|
-
# SAM Template
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
AttributeDefinitions:
|
|
59
|
-
- AttributeName: sessionId
|
|
60
|
-
AttributeType: S
|
|
61
|
-
KeySchema:
|
|
62
|
-
- AttributeName: sessionId
|
|
63
|
-
KeyType: HASH
|
|
64
|
-
TimeToLiveSpecification:
|
|
65
|
-
AttributeName: ttl
|
|
66
|
-
Enabled: true
|
|
67
|
-
|
|
68
|
-
Outputs:
|
|
69
|
-
ApiEndpoint:
|
|
70
|
-
Description: API Gateway endpoint URL
|
|
71
|
-
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/mcp/'
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
// src/lambda.ts — Lambda handler entry point
|
|
76
|
-
import { createLambdaHandler } from '@frontmcp/adapters/lambda';
|
|
77
|
-
import Server from './main';
|
|
78
|
-
|
|
79
|
-
export const handler = createLambdaHandler(Server);
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// src/main.ts
|
|
84
|
-
import { FrontMcp } from '@frontmcp/sdk';
|
|
85
|
-
import { MyApp } from './my.app';
|
|
86
|
-
|
|
87
|
-
@FrontMcp({
|
|
88
|
-
info: { name: 'lambda-mcp', version: '1.0.0' },
|
|
89
|
-
apps: [MyApp],
|
|
90
|
-
cors: {
|
|
91
|
-
origin: ['https://app.example.com'],
|
|
92
|
-
},
|
|
93
|
-
})
|
|
94
|
-
export default class LambdaMcpServer {}
|
|
95
|
-
```
|
|
20
|
+
# SAM Template: Production-Readiness Checklist
|
|
21
|
+
|
|
22
|
+
Checklist for verifying the SAM template pairs correctly with the bundle produced by `frontmcp build --target lambda`. **Note:** configuration authoring lives in `frontmcp-deployment → references/deploy-to-lambda.md`; this file is checklist-only.
|
|
23
|
+
|
|
24
|
+
## Build artifact checks
|
|
25
|
+
|
|
26
|
+
- [ ] `frontmcp build --target lambda` succeeded with no warnings
|
|
27
|
+
- [ ] `dist/lambda/handler.cjs` exists — this is the bundled handler the build adapter writes
|
|
28
|
+
- [ ] No hand-written `src/lambda.ts` importing a fictional `createLambdaHandler` from `@frontmcp/adapters/lambda` — the build adapter generates the entry; your code stays the decorated `@FrontMcp` class
|
|
29
|
+
- [ ] `Handler: handler.handler` with `CodeUri: dist/lambda/` in `template.yaml` (filename `handler.cjs` → handler symbol `handler`). NOT `dist/lambda.handler` or `index.handler`
|
|
30
|
+
- [ ] `CodeUri: .` (or pointed at the project root containing `dist/`) so SAM packages the bundled handler
|
|
31
|
+
|
|
32
|
+
## Function configuration
|
|
33
|
+
|
|
34
|
+
- [ ] `Runtime: nodejs20.x` (or current LTS)
|
|
35
|
+
- [ ] `MemorySize` and `Timeout` sized to your workload (defaults: 256 MB / 30 s)
|
|
36
|
+
- [ ] `Environment.Variables` includes `NODE_ENV: production` and any required app env
|
|
37
|
+
- [ ] Reserved or provisioned concurrency set for latency-sensitive endpoints
|
|
38
|
+
|
|
39
|
+
## Session / state
|
|
40
|
+
|
|
41
|
+
- [ ] DynamoDB session table has `BillingMode: PAY_PER_REQUEST` (or capacity sized correctly)
|
|
42
|
+
- [ ] `TimeToLiveSpecification.AttributeName: ttl` and `Enabled: true` for automatic session cleanup
|
|
43
|
+
- [ ] In `@FrontMcp`, sessions point at DynamoDB / ElastiCache — never in-memory in Lambda
|
|
44
|
+
- [ ] No filesystem writes outside `/tmp` (default 512 MB; configurable up to 10 GB via SAM `EphemeralStorage` if needed)
|
|
45
|
+
|
|
46
|
+
## API Gateway / routing
|
|
47
|
+
|
|
48
|
+
- [ ] Path is `/mcp/{proxy+}` with `Method: ANY` so MCP transport reaches the handler
|
|
49
|
+
- [ ] CORS configured at API Gateway OR via `@FrontMcp` `cors`, not both
|
|
50
|
+
- [ ] Stage names (`Prod` / `Staging`) match deploy pipeline
|
|
51
|
+
|
|
52
|
+
## IAM hardening
|
|
53
|
+
|
|
54
|
+
- [ ] No `Action: '*'` or `Resource: '*'` in the function's policies
|
|
55
|
+
- [ ] DynamoDB access scoped to `!Ref SessionTable` only
|
|
56
|
+
- [ ] Secrets read via SSM / Secrets Manager scoped to `/<app>/<env>/*`
|
|
57
|
+
|
|
58
|
+
## Observability
|
|
59
|
+
|
|
60
|
+
- [ ] CloudWatch alarm on `Errors` metric
|
|
61
|
+
- [ ] CloudWatch alarm on `Throttles` metric
|
|
62
|
+
- [ ] Dead Letter Queue (SQS) configured for failed async invocations
|
|
96
63
|
|
|
97
64
|
## What This Demonstrates
|
|
98
65
|
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
66
|
+
- Verify `Handler: handler.handler` with `CodeUri: dist/lambda/` (the build emits `dist/lambda/handler.cjs`)
|
|
67
|
+
- No hand-written `src/lambda.ts` with a fictional `createLambdaHandler` import
|
|
68
|
+
- DynamoDB session table has TTL enabled for automatic cleanup
|
|
69
|
+
- IAM policies are scoped (no `*` resources / actions)
|
|
70
|
+
- API Gateway proxy route forwards to the function
|
|
104
71
|
|
|
105
72
|
## Related
|
|
106
73
|
|
|
107
|
-
-
|
|
74
|
+
- Configuration source of truth: `frontmcp-deployment/references/deploy-to-lambda.md`
|
|
75
|
+
- Build adapter source: `libs/cli/src/commands/build/adapters/lambda.ts`
|
|
76
|
+
- See `production-lambda` for the runtime / scaling checklist
|
package/catalog/frontmcp-production-readiness/examples/production-lambda/scaling-and-monitoring.md
CHANGED
|
@@ -27,7 +27,7 @@ Resources:
|
|
|
27
27
|
McpFunction:
|
|
28
28
|
Type: AWS::Serverless::Function
|
|
29
29
|
Properties:
|
|
30
|
-
Handler: dist/
|
|
30
|
+
Handler: dist/handler.handler # Build adapter emits dist/handler.cjs → handler symbol
|
|
31
31
|
CodeUri: .
|
|
32
32
|
Runtime: nodejs20.x
|
|
33
33
|
Timeout: 30
|
|
@@ -92,32 +92,42 @@ import { Provider, ProviderScope } from '@frontmcp/sdk';
|
|
|
92
92
|
|
|
93
93
|
export const SECRETS = Symbol('Secrets');
|
|
94
94
|
|
|
95
|
+
// Providers do NOT have onInit/onDestroy — load lazily on first read.
|
|
95
96
|
@Provider({ token: SECRETS, scope: ProviderScope.GLOBAL })
|
|
96
97
|
export class SecretsProvider {
|
|
97
|
-
private cache
|
|
98
|
+
private cache: Map<string, string> | undefined;
|
|
98
99
|
|
|
99
|
-
async
|
|
100
|
-
|
|
100
|
+
private async ensureLoaded(): Promise<Map<string, string>> {
|
|
101
|
+
if (this.cache) return this.cache;
|
|
102
|
+
const cache = new Map<string, string>();
|
|
101
103
|
const { SSMClient, GetParametersByPathCommand } = await import('@aws-sdk/client-ssm');
|
|
102
104
|
const ssm = new SSMClient({});
|
|
103
105
|
const path = process.env.SECRETS_PATH ?? '/mcp/production/';
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
// Walk every page — GetParametersByPath caps results and returns NextToken
|
|
108
|
+
// when more parameters exist. Without this loop, large /<env>/ namespaces
|
|
109
|
+
// silently lose secrets after the first page.
|
|
110
|
+
let nextToken: string | undefined;
|
|
111
|
+
do {
|
|
112
|
+
const result = await ssm.send(
|
|
113
|
+
new GetParametersByPathCommand({ Path: path, WithDecryption: true, NextToken: nextToken }),
|
|
114
|
+
);
|
|
115
|
+
for (const param of result.Parameters ?? []) {
|
|
116
|
+
const key = param.Name?.replace(path, '') ?? '';
|
|
117
|
+
cache.set(key, param.Value ?? '');
|
|
118
|
+
}
|
|
119
|
+
nextToken = result.NextToken;
|
|
120
|
+
} while (nextToken);
|
|
121
|
+
this.cache = cache;
|
|
122
|
+
return cache;
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
get(key: string): string {
|
|
119
|
-
const
|
|
120
|
-
|
|
125
|
+
async get(key: string): Promise<string> {
|
|
126
|
+
const cache = await this.ensureLoaded();
|
|
127
|
+
const value = cache.get(key);
|
|
128
|
+
// `cache.has` / `value === undefined` so empty-string secrets are still
|
|
129
|
+
// returned (a falsy `!value` check would treat `""` as missing).
|
|
130
|
+
if (value === undefined) {
|
|
121
131
|
throw new Error(`Secret not found: ${key}`);
|
|
122
132
|
}
|
|
123
133
|
return value;
|
package/catalog/frontmcp-production-readiness/examples/production-node-sdk/multi-instance-cleanup.md
CHANGED
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
name: multi-instance-cleanup
|
|
3
3
|
reference: production-node-sdk
|
|
4
4
|
level: advanced
|
|
5
|
-
description: 'Shows how multiple SDK instances can coexist without conflicts, and how to
|
|
6
|
-
tags:
|
|
5
|
+
description: 'Shows how multiple SDK instances can coexist without conflicts, and how to clean up timers and listeners — given that `@Provider` classes have **no** `onInit` / `onDestroy` lifecycle hooks. The pattern is: initialize in the constructor, expose an explicit `stop()` method, and have the host app call it before `server.dispose()`.'
|
|
6
|
+
tags:
|
|
7
|
+
- production
|
|
8
|
+
- sdk
|
|
9
|
+
- node
|
|
10
|
+
- multi
|
|
11
|
+
- instance
|
|
12
|
+
- cleanup
|
|
7
13
|
features:
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
14
|
+
- Explicit `stop()` method on providers (since `@Provider` classes have no `onDestroy` lifecycle hook)
|
|
15
|
+
- Ensuring multiple instances coexist without sharing global state
|
|
16
|
+
- Testing that dispose removes all event listeners (no leaks)
|
|
17
|
+
- Verifying one instance still works after another is disposed
|
|
12
18
|
---
|
|
13
19
|
|
|
14
20
|
# Multi-Instance Coexistence and Cleanup
|
|
15
21
|
|
|
16
|
-
Shows how multiple SDK instances can coexist without conflicts, and how to
|
|
22
|
+
Shows how multiple SDK instances can coexist without conflicts, and how to clean up timers and listeners — given that `@Provider` classes have **no** `onInit` / `onDestroy` lifecycle hooks. The pattern is: initialize in the constructor, expose an explicit `stop()` method, and have the host app call it before `server.dispose()`.
|
|
17
23
|
|
|
18
24
|
## Code
|
|
19
25
|
|
|
@@ -28,24 +34,28 @@ export class PollingProvider {
|
|
|
28
34
|
private intervalId: ReturnType<typeof setInterval> | undefined;
|
|
29
35
|
private listeners: Array<() => void> = [];
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
//
|
|
37
|
+
constructor() {
|
|
38
|
+
// Init at construction time — there is no async onInit hook on providers.
|
|
33
39
|
this.intervalId = setInterval(() => {
|
|
34
40
|
this.listeners.forEach((fn) => fn());
|
|
35
41
|
}, 10_000);
|
|
42
|
+
// Don't keep the event loop alive on its own.
|
|
43
|
+
this.intervalId.unref?.();
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
addListener(fn: () => void): void {
|
|
39
47
|
this.listeners.push(fn);
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Explicit cleanup. Host app calls this before `server.dispose()`.
|
|
52
|
+
* (Providers have no onDestroy hook, so this is the explicit pattern.)
|
|
53
|
+
*/
|
|
54
|
+
stop(): void {
|
|
44
55
|
if (this.intervalId) {
|
|
45
56
|
clearInterval(this.intervalId);
|
|
46
57
|
this.intervalId = undefined;
|
|
47
58
|
}
|
|
48
|
-
// Remove all listener references — prevents memory leaks
|
|
49
59
|
this.listeners.length = 0;
|
|
50
60
|
}
|
|
51
61
|
}
|
|
@@ -71,7 +81,11 @@ describe('Multi-instance coexistence', () => {
|
|
|
71
81
|
expect(tools1.tools.length).toBeGreaterThan(0);
|
|
72
82
|
expect(tools2.tools.length).toBeGreaterThan(0);
|
|
73
83
|
|
|
74
|
-
// Clean up
|
|
84
|
+
// Clean up instance 1: stop providers (cancels timers / clears listeners),
|
|
85
|
+
// then dispose the server. The framework does NOT call provider.stop() for
|
|
86
|
+
// you — it's the host app's responsibility, which is why the provider
|
|
87
|
+
// exposes the explicit method.
|
|
88
|
+
server1.scope.providers.get(BackgroundJobProvider).stop();
|
|
75
89
|
await client1.close();
|
|
76
90
|
await server1.dispose();
|
|
77
91
|
|
|
@@ -79,6 +93,7 @@ describe('Multi-instance coexistence', () => {
|
|
|
79
93
|
const result = await client2.callTool('my_tool', { input: 'still-alive' });
|
|
80
94
|
expect(result).toBeDefined();
|
|
81
95
|
|
|
96
|
+
server2.scope.providers.get(BackgroundJobProvider).stop();
|
|
82
97
|
await client2.close();
|
|
83
98
|
await server2.dispose();
|
|
84
99
|
});
|
|
@@ -100,7 +115,7 @@ describe('Multi-instance coexistence', () => {
|
|
|
100
115
|
|
|
101
116
|
## What This Demonstrates
|
|
102
117
|
|
|
103
|
-
-
|
|
118
|
+
- Explicit `stop()` method on providers (since `@Provider` classes have no `onDestroy` lifecycle hook)
|
|
104
119
|
- Ensuring multiple instances coexist without sharing global state
|
|
105
120
|
- Testing that dispose removes all event listeners (no leaks)
|
|
106
121
|
- Verifying one instance still works after another is disposed
|