memory-journal-mcp 6.1.2 → 6.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 +44 -28
- package/dist/{chunk-X4SWFATC.js → chunk-BI4ZNSKA.js} +38 -24
- package/dist/{chunk-HCEWINSB.js → chunk-N6EBIDN7.js} +99 -102
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/tools-WPRY5MJ6.js +2 -0
- package/package.json +10 -1
- package/skills/github-commander/SKILL.md +151 -0
- package/skills/github-commander/config/project-config.example.md +125 -0
- package/skills/github-commander/workflows/code-quality-audit.md +80 -0
- package/skills/github-commander/workflows/full-audit.md +134 -0
- package/skills/github-commander/workflows/issue-triage.md +239 -0
- package/skills/github-commander/workflows/milestone-sprint.md +81 -0
- package/skills/github-commander/workflows/perf-audit.md +142 -0
- package/skills/github-commander/workflows/pr-review.md +123 -0
- package/skills/github-commander/workflows/security-audit.md +170 -0
- package/skills/github-commander/workflows/update-deps.md +109 -0
- package/.dockerignore +0 -139
- package/.gitattributes +0 -20
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -95
- package/.github/ISSUE_TEMPLATE/config.yml +0 -11
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -110
- package/.github/ISSUE_TEMPLATE/question.md +0 -78
- package/.github/aw/actions-lock.json +0 -14
- package/.github/copilot-instructions.md +0 -122
- package/.github/dependabot.yml +0 -93
- package/.github/pull_request_template.md +0 -135
- package/.github/workflows/README.md +0 -133
- package/.github/workflows/agentics-maintenance.yml +0 -141
- package/.github/workflows/auto-release.yml +0 -68
- package/.github/workflows/ci-health-monitor.lock.yml +0 -1121
- package/.github/workflows/ci-health-monitor.md +0 -87
- package/.github/workflows/codeql.yml +0 -41
- package/.github/workflows/dependabot-auto-merge.yml +0 -42
- package/.github/workflows/dependency-maintenance.lock.yml +0 -1182
- package/.github/workflows/dependency-maintenance.md +0 -147
- package/.github/workflows/docker-publish.yml +0 -254
- package/.github/workflows/docs-drift-detector.lock.yml +0 -1142
- package/.github/workflows/docs-drift-detector.md +0 -115
- package/.github/workflows/lint-and-test.yml +0 -60
- package/.github/workflows/publish-npm.yml +0 -85
- package/.github/workflows/secrets-scanning.yml +0 -32
- package/.github/workflows/security-update.yml +0 -127
- package/.gitleaks.toml +0 -9
- package/.prettierignore +0 -21
- package/.prettierrc +0 -33
- package/.scout-ignore +0 -12
- package/.trivyignore +0 -21
- package/CHANGELOG.md +0 -1814
- package/CODE_OF_CONDUCT.md +0 -133
- package/CONTRIBUTING.md +0 -263
- package/DOCKER_README.md +0 -331
- package/Dockerfile +0 -128
- package/SECURITY.md +0 -227
- package/UNRELEASED.md +0 -1
- package/dist/tools-T4U5A3X4.js +0 -2
- package/docker-compose.yml +0 -71
- package/docs/README.md +0 -18
- package/docs/agentic-journal-synergy.md +0 -175
- package/docs/copilot-setup.md +0 -72
- package/eslint.config.js +0 -110
- package/mcp-config-example.json +0 -21
- package/playwright.config.ts +0 -35
- package/releases/v2.1.0.md +0 -220
- package/releases/v2.2.0.md +0 -168
- package/releases/v3.0.0.md +0 -237
- package/releases/v3.1.0.md +0 -104
- package/releases/v3.1.1.md +0 -42
- package/releases/v3.1.2.md +0 -40
- package/releases/v3.1.3.md +0 -64
- package/releases/v3.1.4.md +0 -32
- package/releases/v3.1.5.md +0 -44
- package/releases/v4.0.0.md +0 -71
- package/releases/v4.1.0.md +0 -88
- package/releases/v4.2.0.md +0 -90
- package/releases/v4.3.0.md +0 -92
- package/releases/v4.3.1.md +0 -69
- package/releases/v4.4.0.md +0 -120
- package/releases/v4.4.1.md +0 -33
- package/releases/v4.4.2.md +0 -31
- package/releases/v4.5.0.md +0 -116
- package/releases/v5.0.0.md +0 -105
- package/releases/v5.0.1.md +0 -25
- package/releases/v5.1.0.md +0 -83
- package/releases/v5.1.1.md +0 -10
- package/releases/v6.0.0.md +0 -48
- package/releases/v6.0.1.md +0 -36
- package/releases/v6.1.0.md +0 -68
- package/releases/v6.1.1.md +0 -30
- package/releases/v6.1.2.md +0 -23
- package/scripts/generate-server-instructions.ts +0 -306
- package/scripts/server-instructions-function-body.ts +0 -107
- package/scripts/server-instructions-gotchas.ts +0 -45
- package/server.json +0 -42
- package/social-preview.png +0 -0
- package/src/auth/auth-context.ts +0 -78
- package/src/auth/authorization-server-discovery.ts +0 -263
- package/src/auth/errors.ts +0 -215
- package/src/auth/index.ts +0 -58
- package/src/auth/middleware.ts +0 -392
- package/src/auth/oauth-resource-server.ts +0 -170
- package/src/auth/scope-map.ts +0 -46
- package/src/auth/scopes.ts +0 -256
- package/src/auth/token-validator.ts +0 -293
- package/src/auth/transport-agnostic.ts +0 -164
- package/src/auth/types.ts +0 -372
- package/src/cli.ts +0 -279
- package/src/codemode/api-constants.ts +0 -263
- package/src/codemode/api.ts +0 -302
- package/src/codemode/auto-return.ts +0 -65
- package/src/codemode/index.ts +0 -47
- package/src/codemode/sandbox-factory.ts +0 -144
- package/src/codemode/sandbox.ts +0 -220
- package/src/codemode/security.ts +0 -155
- package/src/codemode/types.ts +0 -228
- package/src/codemode/worker-sandbox.ts +0 -277
- package/src/codemode/worker-script.ts +0 -239
- package/src/constants/icons.ts +0 -183
- package/src/constants/server-instructions.md +0 -166
- package/src/constants/server-instructions.ts +0 -514
- package/src/database/adapter-factory.ts +0 -16
- package/src/database/core/entry-columns.ts +0 -10
- package/src/database/core/interfaces.ts +0 -188
- package/src/database/core/schema.ts +0 -152
- package/src/database/sqlite-adapter/backup.ts +0 -167
- package/src/database/sqlite-adapter/entries/crud.ts +0 -233
- package/src/database/sqlite-adapter/entries/importance.ts +0 -76
- package/src/database/sqlite-adapter/entries/index.ts +0 -142
- package/src/database/sqlite-adapter/entries/search.ts +0 -294
- package/src/database/sqlite-adapter/entries/shared.ts +0 -102
- package/src/database/sqlite-adapter/entries/statistics.ts +0 -162
- package/src/database/sqlite-adapter/index.ts +0 -265
- package/src/database/sqlite-adapter/native-connection.ts +0 -301
- package/src/database/sqlite-adapter/relationships.ts +0 -70
- package/src/database/sqlite-adapter/tags.ts +0 -182
- package/src/filtering/tool-filter.ts +0 -312
- package/src/github/github-integration/client.ts +0 -114
- package/src/github/github-integration/index.ts +0 -297
- package/src/github/github-integration/insights.ts +0 -155
- package/src/github/github-integration/issues.ts +0 -213
- package/src/github/github-integration/milestones.ts +0 -262
- package/src/github/github-integration/projects.ts +0 -414
- package/src/github/github-integration/pull-requests.ts +0 -235
- package/src/github/github-integration/repository.ts +0 -110
- package/src/github/github-integration/types.ts +0 -43
- package/src/handlers/prompts/github.ts +0 -210
- package/src/handlers/prompts/index.ts +0 -97
- package/src/handlers/prompts/workflow.ts +0 -361
- package/src/handlers/resources/core/briefing/context-section.ts +0 -182
- package/src/handlers/resources/core/briefing/github-section.ts +0 -354
- package/src/handlers/resources/core/briefing/index.ts +0 -106
- package/src/handlers/resources/core/briefing/user-message.ts +0 -114
- package/src/handlers/resources/core/health.ts +0 -75
- package/src/handlers/resources/core/index.ts +0 -31
- package/src/handlers/resources/core/instructions.ts +0 -45
- package/src/handlers/resources/core/utilities.ts +0 -310
- package/src/handlers/resources/github.ts +0 -340
- package/src/handlers/resources/graph.ts +0 -218
- package/src/handlers/resources/help.ts +0 -410
- package/src/handlers/resources/index.ts +0 -143
- package/src/handlers/resources/shared.ts +0 -219
- package/src/handlers/resources/team.ts +0 -134
- package/src/handlers/resources/templates.ts +0 -334
- package/src/handlers/tools/admin.ts +0 -351
- package/src/handlers/tools/analytics.ts +0 -346
- package/src/handlers/tools/backup.ts +0 -272
- package/src/handlers/tools/codemode.ts +0 -188
- package/src/handlers/tools/core.ts +0 -359
- package/src/handlers/tools/error-fields-mixin.ts +0 -10
- package/src/handlers/tools/export.ts +0 -150
- package/src/handlers/tools/github/copilot-tools.ts +0 -72
- package/src/handlers/tools/github/helpers.ts +0 -125
- package/src/handlers/tools/github/insights-tools.ts +0 -112
- package/src/handlers/tools/github/issue-tools.ts +0 -442
- package/src/handlers/tools/github/kanban-tools.ts +0 -153
- package/src/handlers/tools/github/milestone-tools.ts +0 -371
- package/src/handlers/tools/github/mutation-tools.ts +0 -17
- package/src/handlers/tools/github/read-tools.ts +0 -302
- package/src/handlers/tools/github/schemas.ts +0 -435
- package/src/handlers/tools/github.ts +0 -39
- package/src/handlers/tools/index.ts +0 -255
- package/src/handlers/tools/relationships.ts +0 -390
- package/src/handlers/tools/schemas.ts +0 -165
- package/src/handlers/tools/search.ts +0 -448
- package/src/handlers/tools/team/admin-tools.ts +0 -164
- package/src/handlers/tools/team/analytics-tools.ts +0 -233
- package/src/handlers/tools/team/backup-tools.ts +0 -83
- package/src/handlers/tools/team/core-tools.ts +0 -197
- package/src/handlers/tools/team/export-tools.ts +0 -130
- package/src/handlers/tools/team/helpers.ts +0 -66
- package/src/handlers/tools/team/index.ts +0 -45
- package/src/handlers/tools/team/relationship-tools.ts +0 -219
- package/src/handlers/tools/team/schemas.ts +0 -558
- package/src/handlers/tools/team/search-tools.ts +0 -145
- package/src/handlers/tools/team/vector-tools.ts +0 -261
- package/src/index.ts +0 -57
- package/src/server/mcp-server.ts +0 -446
- package/src/server/registration.ts +0 -141
- package/src/server/scheduler.ts +0 -283
- package/src/transports/http/handlers.ts +0 -78
- package/src/transports/http/index.ts +0 -8
- package/src/transports/http/security.ts +0 -147
- package/src/transports/http/server/index.ts +0 -397
- package/src/transports/http/server/legacy-sse.ts +0 -87
- package/src/transports/http/server/stateful.ts +0 -222
- package/src/transports/http/server/stateless.ts +0 -42
- package/src/transports/http/types.ts +0 -132
- package/src/types/entities.ts +0 -145
- package/src/types/error-types.ts +0 -92
- package/src/types/errors.ts +0 -200
- package/src/types/filtering.ts +0 -55
- package/src/types/github.ts +0 -216
- package/src/types/index.ts +0 -348
- package/src/utils/error-helpers.ts +0 -78
- package/src/utils/errors/error-response-fields.ts +0 -29
- package/src/utils/errors/suggestions.ts +0 -94
- package/src/utils/github-helpers.ts +0 -33
- package/src/utils/logger.ts +0 -107
- package/src/utils/mcp-logger.ts +0 -155
- package/src/utils/progress-utils.ts +0 -100
- package/src/utils/query-helpers.ts +0 -78
- package/src/utils/resource-annotations.ts +0 -75
- package/src/utils/security-utils.ts +0 -198
- package/src/utils/vector-index-helpers.ts +0 -24
- package/src/vector/vector-search-manager.ts +0 -409
- package/src/version.ts +0 -15
- package/test-server/README.md +0 -193
- package/test-server/code-map.md +0 -399
- package/test-server/test-agent-experience.md +0 -213
- package/test-server/test-filter-instructions.mjs +0 -295
- package/test-server/test-instruction-levels.mjs +0 -102
- package/test-server/test-preflight.md +0 -55
- package/test-server/test-prompts.mjs +0 -185
- package/test-server/test-scheduler.mjs +0 -174
- package/test-server/test-tool-annotations.mjs +0 -115
- package/test-server/test-tools-codemode.md +0 -632
- package/test-server/test-tools-codemode2.md +0 -1218
- package/test-server/test-tools-team.md +0 -215
- package/test-server/test-tools.md +0 -429
- package/test-server/test-tools2.md +0 -361
- package/test-server/test-tools3.md +0 -396
- package/test-server/tool-reference.md +0 -231
- package/tests/README.md +0 -54
- package/tests/auth/auth-context.test.ts +0 -162
- package/tests/auth/authorization-server-discovery.test.ts +0 -265
- package/tests/auth/errors.test.ts +0 -170
- package/tests/auth/middleware.test.ts +0 -585
- package/tests/auth/oauth-resource-server.test.ts +0 -173
- package/tests/auth/scope-map.test.ts +0 -66
- package/tests/auth/scopes.test.ts +0 -347
- package/tests/auth/token-validator.test.ts +0 -271
- package/tests/codemode/api.test.ts +0 -396
- package/tests/codemode/auto-return.test.ts +0 -167
- package/tests/codemode/codemode-tool-handlers.test.ts +0 -197
- package/tests/codemode/sandbox-factory.test.ts +0 -152
- package/tests/codemode/sandbox.test.ts +0 -190
- package/tests/codemode/security.test.ts +0 -242
- package/tests/codemode/worker-sandbox.test.ts +0 -106
- package/tests/constants/icons.test.ts +0 -101
- package/tests/constants/server-instructions.test.ts +0 -514
- package/tests/database/crud-workflow-branches.test.ts +0 -418
- package/tests/database/database-branches.test.ts +0 -132
- package/tests/database/entries-auth-branches.test.ts +0 -390
- package/tests/database/native-connection.test.ts +0 -249
- package/tests/database/shared-helpers.test.ts +0 -103
- package/tests/database/sqlite-adapter.bench.ts +0 -63
- package/tests/database/sqlite-adapter.test.ts +0 -690
- package/tests/database/tags.test.ts +0 -134
- package/tests/e2e/README.md +0 -39
- package/tests/e2e/auth.spec.ts +0 -106
- package/tests/e2e/codemode-abuse.spec.ts +0 -75
- package/tests/e2e/health.spec.ts +0 -63
- package/tests/e2e/helpers.ts +0 -139
- package/tests/e2e/oauth-discovery.spec.ts +0 -102
- package/tests/e2e/oauth-scopes.spec.ts +0 -222
- package/tests/e2e/payloads-admin.spec.ts +0 -76
- package/tests/e2e/payloads-analytics.spec.ts +0 -37
- package/tests/e2e/payloads-backup-restore.spec.ts +0 -102
- package/tests/e2e/payloads-backup.spec.ts +0 -44
- package/tests/e2e/payloads-codemode-api.spec.ts +0 -131
- package/tests/e2e/payloads-codemode-readonly.spec.ts +0 -116
- package/tests/e2e/payloads-codemode.spec.ts +0 -116
- package/tests/e2e/payloads-core.spec.ts +0 -82
- package/tests/e2e/payloads-error-contracts.spec.ts +0 -159
- package/tests/e2e/payloads-export.spec.ts +0 -46
- package/tests/e2e/payloads-github-degradation.spec.ts +0 -73
- package/tests/e2e/payloads-github.spec.ts +0 -176
- package/tests/e2e/payloads-relationships.spec.ts +0 -56
- package/tests/e2e/payloads-search.spec.ts +0 -64
- package/tests/e2e/payloads-team-happy.spec.ts +0 -231
- package/tests/e2e/payloads-team.spec.ts +0 -174
- package/tests/e2e/prompts-expanded.spec.ts +0 -137
- package/tests/e2e/prompts.spec.ts +0 -62
- package/tests/e2e/protocols.spec.ts +0 -134
- package/tests/e2e/rate-limiting.spec.ts +0 -291
- package/tests/e2e/resources-briefing-env.spec.ts +0 -106
- package/tests/e2e/resources-complete.spec.ts +0 -180
- package/tests/e2e/resources-expanded.spec.ts +0 -83
- package/tests/e2e/resources-instructions-levels.spec.ts +0 -145
- package/tests/e2e/resources-templates.spec.ts +0 -123
- package/tests/e2e/resources.spec.ts +0 -103
- package/tests/e2e/scheduler.spec.ts +0 -79
- package/tests/e2e/security.spec.ts +0 -112
- package/tests/e2e/session-advanced.spec.ts +0 -152
- package/tests/e2e/sessions.spec.ts +0 -95
- package/tests/e2e/stateless.spec.ts +0 -79
- package/tests/e2e/streaming.spec.ts +0 -176
- package/tests/e2e/tool-filtering-presets.spec.ts +0 -192
- package/tests/e2e/tool-filtering.spec.ts +0 -77
- package/tests/e2e/tools.spec.ts +0 -111
- package/tests/filtering/tool-filter.test.ts +0 -314
- package/tests/github/client-issues-errors.test.ts +0 -433
- package/tests/github/github-integration-branches.test.ts +0 -490
- package/tests/github/github-integration.test.ts +0 -1015
- package/tests/github/github-managers-branches.test.ts +0 -907
- package/tests/github/pull-requests.test.ts +0 -334
- package/tests/handlers/analytics-branches.test.ts +0 -222
- package/tests/handlers/backup-branches.test.ts +0 -270
- package/tests/handlers/briefing-context-section.test.ts +0 -388
- package/tests/handlers/briefing-github-section.test.ts +0 -392
- package/tests/handlers/briefing-user-message.test.ts +0 -405
- package/tests/handlers/codemode-tools.test.ts +0 -85
- package/tests/handlers/copilot-tools.test.ts +0 -126
- package/tests/handlers/error-path-coverage.test.ts +0 -324
- package/tests/handlers/export-tools.test.ts +0 -203
- package/tests/handlers/github-resource-handlers.test.ts +0 -929
- package/tests/handlers/github-tool-handlers.test.ts +0 -1452
- package/tests/handlers/handler-error-branches.test.ts +0 -346
- package/tests/handlers/help-resource.test.ts +0 -92
- package/tests/handlers/prompt-handler-coverage.test.ts +0 -108
- package/tests/handlers/prompt-handlers.test.ts +0 -131
- package/tests/handlers/resource-handler-coverage.test.ts +0 -281
- package/tests/handlers/resource-handlers.test.ts +0 -357
- package/tests/handlers/resource-prompt-branches.test.ts +0 -495
- package/tests/handlers/search-tool-handlers.test.ts +0 -379
- package/tests/handlers/targeted-gap-closure.test.ts +0 -387
- package/tests/handlers/team-admin.test.ts +0 -291
- package/tests/handlers/team-analytics.test.ts +0 -220
- package/tests/handlers/team-core.test.ts +0 -148
- package/tests/handlers/team-data.test.ts +0 -198
- package/tests/handlers/team-relationships.test.ts +0 -271
- package/tests/handlers/team-resource-handlers.test.ts +0 -161
- package/tests/handlers/team-search.test.ts +0 -134
- package/tests/handlers/team-tool-handlers.test.ts +0 -301
- package/tests/handlers/team-vector.test.ts +0 -213
- package/tests/handlers/template-github-branches.test.ts +0 -676
- package/tests/handlers/tool-annotations.test.ts +0 -90
- package/tests/handlers/tool-handler-coverage.test.ts +0 -514
- package/tests/handlers/tool-handlers.test.ts +0 -510
- package/tests/handlers/tool-output-schemas.test.ts +0 -116
- package/tests/handlers/vector-tool-handlers.test.ts +0 -238
- package/tests/security/sql-injection.test.ts +0 -284
- package/tests/server/mcp-server.bench.ts +0 -55
- package/tests/server/mcp-server.test.ts +0 -1326
- package/tests/server/scheduler.test.ts +0 -400
- package/tests/transports/http-legacy-sse.test.ts +0 -275
- package/tests/transports/http-security.test.ts +0 -322
- package/tests/transports/http-stateful.test.ts +0 -487
- package/tests/transports/http-transport-server.test.ts +0 -301
- package/tests/transports/http-transport.test.ts +0 -771
- package/tests/utils/github-helpers.test.ts +0 -58
- package/tests/utils/logger.test.ts +0 -180
- package/tests/utils/mcp-logger.test.ts +0 -211
- package/tests/utils/progress-utils.test.ts +0 -156
- package/tests/utils/query-helpers.test.ts +0 -80
- package/tests/utils/security-utils.test.ts +0 -82
- package/tests/vector/vector-search-branches.test.ts +0 -111
- package/tests/vector/vector-search-manager.test.ts +0 -375
- package/tests/vector/vector-search.bench.ts +0 -48
- package/tsconfig.json +0 -42
- package/tsup.config.ts +0 -19
- package/vitest.config.ts +0 -25
|
@@ -1,771 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP Transport Coverage Tests
|
|
3
|
-
*
|
|
4
|
-
* Unit tests for HttpTransport class with mocked Express and MCP SDK.
|
|
5
|
-
* Focuses on easy coverage gains: constructor, stop(), middleware behavior,
|
|
6
|
-
* and route handler logic — without spinning up real HTTP servers.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Hoisted mocks — vi.hoisted ensures these are available before vi.mock
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
|
-
const {
|
|
16
|
-
mockRoutes,
|
|
17
|
-
mockMiddlewares,
|
|
18
|
-
mockApp,
|
|
19
|
-
MockStreamableHTTPServerTransport,
|
|
20
|
-
MockSSEServerTransport,
|
|
21
|
-
} = vi.hoisted(() => {
|
|
22
|
-
const handleRequest = vi.fn().mockResolvedValue(undefined)
|
|
23
|
-
const transportClose = vi.fn().mockResolvedValue(undefined)
|
|
24
|
-
|
|
25
|
-
const routes: Record<string, Record<string, Function>> = {
|
|
26
|
-
get: {},
|
|
27
|
-
post: {},
|
|
28
|
-
delete: {},
|
|
29
|
-
all: {},
|
|
30
|
-
}
|
|
31
|
-
const middlewares: Function[] = []
|
|
32
|
-
|
|
33
|
-
const app = {
|
|
34
|
-
use: vi.fn().mockImplementation((...args: unknown[]) => {
|
|
35
|
-
if (args.length === 1 && typeof args[0] === 'function') {
|
|
36
|
-
middlewares.push(args[0] as Function)
|
|
37
|
-
}
|
|
38
|
-
}),
|
|
39
|
-
get: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
40
|
-
routes['get']![path] = handler
|
|
41
|
-
}),
|
|
42
|
-
post: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
43
|
-
routes['post']![path] = handler
|
|
44
|
-
}),
|
|
45
|
-
delete: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
46
|
-
routes['delete']![path] = handler
|
|
47
|
-
}),
|
|
48
|
-
all: vi.fn().mockImplementation((path: string, handler: Function) => {
|
|
49
|
-
routes['all']![path] = handler
|
|
50
|
-
}),
|
|
51
|
-
listen: vi.fn().mockImplementation((_port: number, _host: string, cb?: () => void) => {
|
|
52
|
-
if (cb) cb()
|
|
53
|
-
return { close: vi.fn(), on: vi.fn() }
|
|
54
|
-
}),
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Class-based mocks — classes work correctly with `new` operator
|
|
58
|
-
class StreamableMock {
|
|
59
|
-
sessionId: string
|
|
60
|
-
handleRequest = handleRequest
|
|
61
|
-
close = transportClose
|
|
62
|
-
onclose: (() => void) | null = null
|
|
63
|
-
constructor(opts?: {
|
|
64
|
-
sessionIdGenerator?: () => string
|
|
65
|
-
onsessioninitialized?: (sid: string) => void
|
|
66
|
-
}) {
|
|
67
|
-
this.sessionId = opts?.sessionIdGenerator?.() ?? 'mock-session-id'
|
|
68
|
-
if (opts?.onsessioninitialized) {
|
|
69
|
-
setTimeout(() => opts.onsessioninitialized!(this.sessionId), 0)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
class SSEMock {
|
|
75
|
-
sessionId = 'sse-mock-session'
|
|
76
|
-
handlePostMessage = vi.fn().mockResolvedValue(undefined)
|
|
77
|
-
close = transportClose
|
|
78
|
-
onclose: (() => void) | null = null
|
|
79
|
-
start = vi.fn().mockResolvedValue(undefined)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
mockHandleRequest: handleRequest,
|
|
84
|
-
mockTransportClose: transportClose,
|
|
85
|
-
mockRoutes: routes,
|
|
86
|
-
mockMiddlewares: middlewares,
|
|
87
|
-
mockApp: app,
|
|
88
|
-
MockStreamableHTTPServerTransport: StreamableMock,
|
|
89
|
-
MockSSEServerTransport: SSEMock,
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// ============================================================================
|
|
94
|
-
// Module mocks
|
|
95
|
-
// ============================================================================
|
|
96
|
-
|
|
97
|
-
vi.mock('@modelcontextprotocol/sdk/server/streamableHttp.js', () => ({
|
|
98
|
-
StreamableHTTPServerTransport: MockStreamableHTTPServerTransport,
|
|
99
|
-
}))
|
|
100
|
-
|
|
101
|
-
vi.mock('@modelcontextprotocol/sdk/server/sse.js', () => ({
|
|
102
|
-
SSEServerTransport: MockSSEServerTransport,
|
|
103
|
-
}))
|
|
104
|
-
|
|
105
|
-
vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
|
|
106
|
-
isInitializeRequest: vi.fn().mockReturnValue(false),
|
|
107
|
-
}))
|
|
108
|
-
|
|
109
|
-
vi.mock('express', () => {
|
|
110
|
-
const expressFn = vi.fn().mockReturnValue(mockApp)
|
|
111
|
-
return {
|
|
112
|
-
default: Object.assign(expressFn, {
|
|
113
|
-
json: vi.fn().mockReturnValue(vi.fn()),
|
|
114
|
-
}),
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
vi.mock('../../src/utils/logger.js', () => ({
|
|
119
|
-
logger: {
|
|
120
|
-
info: vi.fn(),
|
|
121
|
-
warning: vi.fn(),
|
|
122
|
-
error: vi.fn(),
|
|
123
|
-
debug: vi.fn(),
|
|
124
|
-
},
|
|
125
|
-
}))
|
|
126
|
-
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// Import after mocks
|
|
129
|
-
// ============================================================================
|
|
130
|
-
|
|
131
|
-
import { HttpTransport, type HttpTransportConfig } from '../../src/transports/http/index.js'
|
|
132
|
-
|
|
133
|
-
// ============================================================================
|
|
134
|
-
// Helpers
|
|
135
|
-
// ============================================================================
|
|
136
|
-
|
|
137
|
-
function mockReq(overrides: Partial<Record<string, unknown>> = {}): Record<string, unknown> {
|
|
138
|
-
return {
|
|
139
|
-
method: 'GET',
|
|
140
|
-
path: '/',
|
|
141
|
-
headers: {},
|
|
142
|
-
query: {},
|
|
143
|
-
body: {},
|
|
144
|
-
on: vi.fn(),
|
|
145
|
-
ip: '127.0.0.1',
|
|
146
|
-
socket: { remoteAddress: '127.0.0.1' },
|
|
147
|
-
...overrides,
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function mockRes(): Record<string, unknown> {
|
|
152
|
-
const res: Record<string, unknown> = {
|
|
153
|
-
headersSent: false,
|
|
154
|
-
setHeader: vi.fn(),
|
|
155
|
-
json: vi.fn(),
|
|
156
|
-
send: vi.fn(),
|
|
157
|
-
end: vi.fn(),
|
|
158
|
-
}
|
|
159
|
-
// Make status().json() / status().end() / status().send() chain work
|
|
160
|
-
res['status'] = vi.fn().mockReturnValue(res)
|
|
161
|
-
return res
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ============================================================================
|
|
165
|
-
// Tests
|
|
166
|
-
// ============================================================================
|
|
167
|
-
|
|
168
|
-
describe('HttpTransport', () => {
|
|
169
|
-
const mockServer = {
|
|
170
|
-
connect: vi.fn().mockResolvedValue(undefined),
|
|
171
|
-
close: vi.fn().mockResolvedValue(undefined),
|
|
172
|
-
} as unknown as Parameters<HttpTransport['start']>[0]
|
|
173
|
-
|
|
174
|
-
beforeEach(() => {
|
|
175
|
-
vi.clearAllMocks()
|
|
176
|
-
for (const method of Object.keys(mockRoutes)) {
|
|
177
|
-
mockRoutes[method] = {}
|
|
178
|
-
}
|
|
179
|
-
mockMiddlewares.length = 0
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
// ========================================================================
|
|
183
|
-
// Constructor
|
|
184
|
-
// ========================================================================
|
|
185
|
-
|
|
186
|
-
describe('constructor', () => {
|
|
187
|
-
it('should create instance with config', () => {
|
|
188
|
-
const config: HttpTransportConfig = {
|
|
189
|
-
port: 3000,
|
|
190
|
-
host: '0.0.0.0',
|
|
191
|
-
corsOrigins: ['*'],
|
|
192
|
-
stateless: false,
|
|
193
|
-
}
|
|
194
|
-
const transport = new HttpTransport(config)
|
|
195
|
-
expect(transport).toBeDefined()
|
|
196
|
-
})
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
// ========================================================================
|
|
200
|
-
// start — stateless
|
|
201
|
-
// ========================================================================
|
|
202
|
-
|
|
203
|
-
describe('start - stateless mode', () => {
|
|
204
|
-
it('should register routes and start server', async () => {
|
|
205
|
-
const config: HttpTransportConfig = {
|
|
206
|
-
port: 3000,
|
|
207
|
-
host: '0.0.0.0',
|
|
208
|
-
corsOrigins: ['*'],
|
|
209
|
-
stateless: true,
|
|
210
|
-
}
|
|
211
|
-
const transport = new HttpTransport(config)
|
|
212
|
-
await transport.start(mockServer, null)
|
|
213
|
-
|
|
214
|
-
expect(mockApp.use).toHaveBeenCalled()
|
|
215
|
-
expect(mockApp.listen).toHaveBeenCalledWith(3000, '0.0.0.0', expect.any(Function))
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
it('should warn about wildcard CORS and no auth', async () => {
|
|
219
|
-
const { logger } = await import('../../src/utils/logger.js')
|
|
220
|
-
const config: HttpTransportConfig = {
|
|
221
|
-
port: 3000,
|
|
222
|
-
host: '0.0.0.0',
|
|
223
|
-
corsOrigins: ['*'],
|
|
224
|
-
stateless: true,
|
|
225
|
-
}
|
|
226
|
-
const transport = new HttpTransport(config)
|
|
227
|
-
await transport.start(mockServer, null)
|
|
228
|
-
|
|
229
|
-
expect(logger.warning).toHaveBeenCalledWith(
|
|
230
|
-
expect.stringContaining('CORS origin'),
|
|
231
|
-
expect.any(Object)
|
|
232
|
-
)
|
|
233
|
-
expect(logger.warning).toHaveBeenCalledWith(
|
|
234
|
-
expect.stringContaining('No authentication'),
|
|
235
|
-
expect.any(Object)
|
|
236
|
-
)
|
|
237
|
-
})
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
// ========================================================================
|
|
241
|
-
// start — stateful with auth
|
|
242
|
-
// ========================================================================
|
|
243
|
-
|
|
244
|
-
describe('start - stateful mode with auth', () => {
|
|
245
|
-
it('should setup auth middleware when token provided', async () => {
|
|
246
|
-
const config: HttpTransportConfig = {
|
|
247
|
-
port: 3000,
|
|
248
|
-
host: '0.0.0.0',
|
|
249
|
-
corsOrigin: 'http://localhost',
|
|
250
|
-
stateless: false,
|
|
251
|
-
authToken: 'test-token-123',
|
|
252
|
-
}
|
|
253
|
-
const transport = new HttpTransport(config)
|
|
254
|
-
await transport.start(mockServer, null)
|
|
255
|
-
|
|
256
|
-
expect(mockApp.use).toHaveBeenCalled()
|
|
257
|
-
})
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
// ========================================================================
|
|
261
|
-
// start — middleware behavior
|
|
262
|
-
// ========================================================================
|
|
263
|
-
|
|
264
|
-
describe('middleware behavior', () => {
|
|
265
|
-
/** Find a middleware by probing: returns the first mw whose invocation triggers the predicate. */
|
|
266
|
-
function findMiddleware(
|
|
267
|
-
predicate: (res: Record<string, unknown>) => boolean
|
|
268
|
-
): Function | undefined {
|
|
269
|
-
for (const mw of mockMiddlewares) {
|
|
270
|
-
const req = mockReq()
|
|
271
|
-
const res = mockRes()
|
|
272
|
-
try {
|
|
273
|
-
mw(req, res, () => {})
|
|
274
|
-
} catch {
|
|
275
|
-
/* hostHeaderValidation may throw */
|
|
276
|
-
}
|
|
277
|
-
if (predicate(res)) return mw
|
|
278
|
-
}
|
|
279
|
-
return undefined
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
it('should set security headers on requests', async () => {
|
|
283
|
-
const config: HttpTransportConfig = {
|
|
284
|
-
port: 3000,
|
|
285
|
-
host: '0.0.0.0',
|
|
286
|
-
corsOrigins: ['*'],
|
|
287
|
-
stateless: true,
|
|
288
|
-
}
|
|
289
|
-
const transport = new HttpTransport(config)
|
|
290
|
-
await transport.start(mockServer, null)
|
|
291
|
-
|
|
292
|
-
// Find middleware that sets security headers (position is dynamic due to hostHeaderValidation)
|
|
293
|
-
const securityMw = findMiddleware((res) => {
|
|
294
|
-
const calls = (res['setHeader'] as ReturnType<typeof vi.fn>).mock.calls as [
|
|
295
|
-
string,
|
|
296
|
-
string,
|
|
297
|
-
][]
|
|
298
|
-
return calls.some((c) => c[0] === 'X-Content-Type-Options')
|
|
299
|
-
})
|
|
300
|
-
expect(securityMw).toBeDefined()
|
|
301
|
-
|
|
302
|
-
const req = mockReq()
|
|
303
|
-
const res = mockRes()
|
|
304
|
-
securityMw!(req, res, () => {})
|
|
305
|
-
|
|
306
|
-
const setCalls = (res['setHeader'] as ReturnType<typeof vi.fn>).mock.calls
|
|
307
|
-
const headerNames = setCalls.map((c: unknown[]) => c[0] as string)
|
|
308
|
-
expect(headerNames).toContain('X-Content-Type-Options')
|
|
309
|
-
expect(headerNames).toContain('X-Frame-Options')
|
|
310
|
-
expect(headerNames).toContain('Cache-Control')
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
it('should set HSTS when enableHSTS is true', async () => {
|
|
314
|
-
const config: HttpTransportConfig = {
|
|
315
|
-
port: 3000,
|
|
316
|
-
host: '0.0.0.0',
|
|
317
|
-
corsOrigins: ['*'],
|
|
318
|
-
stateless: true,
|
|
319
|
-
enableHSTS: true,
|
|
320
|
-
}
|
|
321
|
-
const transport = new HttpTransport(config)
|
|
322
|
-
await transport.start(mockServer, null)
|
|
323
|
-
|
|
324
|
-
// Find security headers middleware by behavior
|
|
325
|
-
const securityMw = findMiddleware((res) => {
|
|
326
|
-
const calls = (res['setHeader'] as ReturnType<typeof vi.fn>).mock.calls as [
|
|
327
|
-
string,
|
|
328
|
-
string,
|
|
329
|
-
][]
|
|
330
|
-
return calls.some((c) => c[0] === 'Strict-Transport-Security')
|
|
331
|
-
})
|
|
332
|
-
expect(securityMw).toBeDefined()
|
|
333
|
-
|
|
334
|
-
const req = mockReq()
|
|
335
|
-
const res = mockRes()
|
|
336
|
-
securityMw!(req, res, () => {})
|
|
337
|
-
|
|
338
|
-
const setCalls = (res['setHeader'] as ReturnType<typeof vi.fn>).mock.calls
|
|
339
|
-
const headerNames = setCalls.map((c: unknown[]) => c[0] as string)
|
|
340
|
-
expect(headerNames).toContain('Strict-Transport-Security')
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
it('should handle CORS OPTIONS preflight with 204', async () => {
|
|
344
|
-
const config: HttpTransportConfig = {
|
|
345
|
-
port: 3000,
|
|
346
|
-
host: '0.0.0.0',
|
|
347
|
-
corsOrigins: ['*'],
|
|
348
|
-
stateless: true,
|
|
349
|
-
}
|
|
350
|
-
const transport = new HttpTransport(config)
|
|
351
|
-
await transport.start(mockServer, null)
|
|
352
|
-
|
|
353
|
-
// Find the OPTIONS middleware by behavior (returns 204 for OPTIONS)
|
|
354
|
-
let optionsMw: Function | undefined
|
|
355
|
-
for (const mw of mockMiddlewares) {
|
|
356
|
-
const req = mockReq({ method: 'OPTIONS' })
|
|
357
|
-
const res = mockRes()
|
|
358
|
-
try {
|
|
359
|
-
mw(req, res, () => {})
|
|
360
|
-
} catch {
|
|
361
|
-
/* skip */
|
|
362
|
-
}
|
|
363
|
-
if (
|
|
364
|
-
(res['status'] as ReturnType<typeof vi.fn>).mock.calls.some(
|
|
365
|
-
(c: unknown[]) => c[0] === 204
|
|
366
|
-
)
|
|
367
|
-
) {
|
|
368
|
-
optionsMw = mw
|
|
369
|
-
break
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
expect(optionsMw).toBeDefined()
|
|
373
|
-
|
|
374
|
-
const req = mockReq({ method: 'OPTIONS' })
|
|
375
|
-
const res = mockRes()
|
|
376
|
-
optionsMw!(req, res, () => {})
|
|
377
|
-
|
|
378
|
-
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(204)
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
it('should pass non-OPTIONS requests through CORS middleware', async () => {
|
|
382
|
-
const config: HttpTransportConfig = {
|
|
383
|
-
port: 3000,
|
|
384
|
-
host: '0.0.0.0',
|
|
385
|
-
corsOrigins: ['*'],
|
|
386
|
-
stateless: true,
|
|
387
|
-
}
|
|
388
|
-
const transport = new HttpTransport(config)
|
|
389
|
-
await transport.start(mockServer, null)
|
|
390
|
-
|
|
391
|
-
const corsMw = mockMiddlewares[1]
|
|
392
|
-
const req = mockReq({ method: 'POST' })
|
|
393
|
-
const res = mockRes()
|
|
394
|
-
let nextCalled = false
|
|
395
|
-
corsMw!(req, res, () => {
|
|
396
|
-
nextCalled = true
|
|
397
|
-
})
|
|
398
|
-
expect(nextCalled).toBe(true)
|
|
399
|
-
})
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
// ========================================================================
|
|
403
|
-
// start — route handlers
|
|
404
|
-
// ========================================================================
|
|
405
|
-
|
|
406
|
-
describe('route handlers', () => {
|
|
407
|
-
it('should return healthy on GET /health', async () => {
|
|
408
|
-
const config: HttpTransportConfig = {
|
|
409
|
-
port: 3000,
|
|
410
|
-
host: '0.0.0.0',
|
|
411
|
-
corsOrigins: ['*'],
|
|
412
|
-
stateless: true,
|
|
413
|
-
}
|
|
414
|
-
const transport = new HttpTransport(config)
|
|
415
|
-
await transport.start(mockServer, null)
|
|
416
|
-
|
|
417
|
-
const handler = mockRoutes['get']!['/health']
|
|
418
|
-
expect(handler).toBeDefined()
|
|
419
|
-
|
|
420
|
-
const res = mockRes()
|
|
421
|
-
handler!(mockReq(), res)
|
|
422
|
-
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(200)
|
|
423
|
-
expect(res['json'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(
|
|
424
|
-
expect.objectContaining({ status: 'healthy' })
|
|
425
|
-
)
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
it('should return server info on GET /', async () => {
|
|
429
|
-
const config: HttpTransportConfig = {
|
|
430
|
-
port: 3000,
|
|
431
|
-
host: '0.0.0.0',
|
|
432
|
-
corsOrigins: ['*'],
|
|
433
|
-
stateless: true,
|
|
434
|
-
}
|
|
435
|
-
const transport = new HttpTransport(config)
|
|
436
|
-
await transport.start(mockServer, null)
|
|
437
|
-
|
|
438
|
-
const handler = mockRoutes['get']!['/']
|
|
439
|
-
expect(handler).toBeDefined()
|
|
440
|
-
|
|
441
|
-
const res = mockRes()
|
|
442
|
-
handler!(mockReq(), res)
|
|
443
|
-
expect(res['json'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(
|
|
444
|
-
expect.objectContaining({ name: 'memory-journal-mcp' })
|
|
445
|
-
)
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
it('should return 204 for OPTIONS via middleware', async () => {
|
|
449
|
-
const config: HttpTransportConfig = {
|
|
450
|
-
port: 3000,
|
|
451
|
-
host: '0.0.0.0',
|
|
452
|
-
corsOrigins: ['*'],
|
|
453
|
-
stateless: true,
|
|
454
|
-
}
|
|
455
|
-
const transport = new HttpTransport(config)
|
|
456
|
-
await transport.start(mockServer, null)
|
|
457
|
-
|
|
458
|
-
// Find the OPTIONS middleware by behavior (position is dynamic)
|
|
459
|
-
let optionsMw: Function | undefined
|
|
460
|
-
for (const mw of mockMiddlewares) {
|
|
461
|
-
const probe = mockRes()
|
|
462
|
-
try {
|
|
463
|
-
mw(mockReq({ method: 'OPTIONS' }), probe, () => {})
|
|
464
|
-
} catch {
|
|
465
|
-
/* skip */
|
|
466
|
-
}
|
|
467
|
-
if (
|
|
468
|
-
(probe['status'] as ReturnType<typeof vi.fn>).mock.calls.some(
|
|
469
|
-
(c: unknown[]) => c[0] === 204
|
|
470
|
-
)
|
|
471
|
-
) {
|
|
472
|
-
optionsMw = mw
|
|
473
|
-
break
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
expect(optionsMw).toBeDefined()
|
|
477
|
-
|
|
478
|
-
const req = mockReq({ method: 'OPTIONS' })
|
|
479
|
-
const res = mockRes()
|
|
480
|
-
const next = vi.fn()
|
|
481
|
-
optionsMw!(req, res, next)
|
|
482
|
-
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(204)
|
|
483
|
-
expect(next).not.toHaveBeenCalled()
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
it('should pass non-OPTIONS requests through OPTIONS middleware', async () => {
|
|
487
|
-
const config: HttpTransportConfig = {
|
|
488
|
-
port: 3000,
|
|
489
|
-
host: '0.0.0.0',
|
|
490
|
-
corsOrigins: ['*'],
|
|
491
|
-
stateless: true,
|
|
492
|
-
}
|
|
493
|
-
const transport = new HttpTransport(config)
|
|
494
|
-
await transport.start(mockServer, null)
|
|
495
|
-
|
|
496
|
-
const optionsMw = mockMiddlewares[1]
|
|
497
|
-
const req = mockReq({ method: 'POST' })
|
|
498
|
-
const res = mockRes()
|
|
499
|
-
let nextCalled = false
|
|
500
|
-
optionsMw!(req, res, () => {
|
|
501
|
-
nextCalled = true
|
|
502
|
-
})
|
|
503
|
-
expect(nextCalled).toBe(true)
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
it('should return 404 for unknown routes', async () => {
|
|
507
|
-
const config: HttpTransportConfig = {
|
|
508
|
-
port: 3000,
|
|
509
|
-
host: '0.0.0.0',
|
|
510
|
-
corsOrigins: ['*'],
|
|
511
|
-
stateless: true,
|
|
512
|
-
}
|
|
513
|
-
const transport = new HttpTransport(config)
|
|
514
|
-
await transport.start(mockServer, null)
|
|
515
|
-
|
|
516
|
-
// 404 handler is the last registered middleware
|
|
517
|
-
const lastMw = mockMiddlewares[mockMiddlewares.length - 1]
|
|
518
|
-
expect(lastMw).toBeDefined()
|
|
519
|
-
|
|
520
|
-
const res = mockRes()
|
|
521
|
-
lastMw!(mockReq(), res)
|
|
522
|
-
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(404)
|
|
523
|
-
})
|
|
524
|
-
})
|
|
525
|
-
|
|
526
|
-
// ========================================================================
|
|
527
|
-
// start — stateless /mcp routes
|
|
528
|
-
// ========================================================================
|
|
529
|
-
|
|
530
|
-
describe('stateless /mcp routes', () => {
|
|
531
|
-
it('should return 405 for GET /mcp in stateless mode', async () => {
|
|
532
|
-
const config: HttpTransportConfig = {
|
|
533
|
-
port: 3000,
|
|
534
|
-
host: '0.0.0.0',
|
|
535
|
-
corsOrigins: ['*'],
|
|
536
|
-
stateless: true,
|
|
537
|
-
}
|
|
538
|
-
const transport = new HttpTransport(config)
|
|
539
|
-
await transport.start(mockServer, null)
|
|
540
|
-
|
|
541
|
-
const handler = mockRoutes['get']!['/mcp']
|
|
542
|
-
expect(handler).toBeDefined()
|
|
543
|
-
|
|
544
|
-
const res = mockRes()
|
|
545
|
-
handler!(mockReq(), res)
|
|
546
|
-
expect(res['status'] as ReturnType<typeof vi.fn>).toHaveBeenCalledWith(405)
|
|
547
|
-
})
|
|
548
|
-
|
|
549
|
-
it('should return 204 for DELETE /mcp in stateless mode', async () => {
|
|
550
|
-
const config: HttpTransportConfig = {
|
|
551
|
-
port: 3000,
|
|
552
|
-
host: '0.0.0.0',
|
|
553
|
-
corsOrigins: ['*'],
|
|
554
|
-
stateless: true,
|
|
555
|
-
}
|
|
556
|
-
const transport = new HttpTransport(config)
|
|
557
|
-
await transport.start(mockServer, null)
|
|
558
|
-
|
|
559
|
-
const handler = mockRoutes['delete']!['/mcp']
|
|
560
|
-
expect(handler).toBeDefined()
|
|
561
|
-
|
|
562
|
-
const res = mockRes()
|
|
563
|
-
handler!(mockReq(), res)
|
|
564
|
-
expect(res['end'] as ReturnType<typeof vi.fn>).toHaveBeenCalled()
|
|
565
|
-
})
|
|
566
|
-
})
|
|
567
|
-
|
|
568
|
-
// ========================================================================
|
|
569
|
-
// start — scheduler
|
|
570
|
-
// ========================================================================
|
|
571
|
-
|
|
572
|
-
describe('scheduler', () => {
|
|
573
|
-
it('should start scheduler after server listen', async () => {
|
|
574
|
-
const config: HttpTransportConfig = {
|
|
575
|
-
port: 3000,
|
|
576
|
-
host: '0.0.0.0',
|
|
577
|
-
corsOrigins: ['*'],
|
|
578
|
-
stateless: true,
|
|
579
|
-
}
|
|
580
|
-
const mockScheduler = { start: vi.fn(), stop: vi.fn() }
|
|
581
|
-
const transport = new HttpTransport(config)
|
|
582
|
-
await transport.start(
|
|
583
|
-
mockServer,
|
|
584
|
-
mockScheduler as unknown as Parameters<HttpTransport['start']>[1]
|
|
585
|
-
)
|
|
586
|
-
|
|
587
|
-
expect(mockScheduler.start).toHaveBeenCalled()
|
|
588
|
-
})
|
|
589
|
-
})
|
|
590
|
-
|
|
591
|
-
// ========================================================================
|
|
592
|
-
// stop
|
|
593
|
-
// ========================================================================
|
|
594
|
-
|
|
595
|
-
describe('stop', () => {
|
|
596
|
-
it('should clean up on stop', async () => {
|
|
597
|
-
const config: HttpTransportConfig = {
|
|
598
|
-
port: 3000,
|
|
599
|
-
host: '0.0.0.0',
|
|
600
|
-
corsOrigins: ['*'],
|
|
601
|
-
stateless: true,
|
|
602
|
-
}
|
|
603
|
-
const transport = new HttpTransport(config)
|
|
604
|
-
await transport.start(mockServer, null)
|
|
605
|
-
await transport.stop(null)
|
|
606
|
-
// Should not throw
|
|
607
|
-
})
|
|
608
|
-
|
|
609
|
-
it('should stop scheduler on stop', async () => {
|
|
610
|
-
const config: HttpTransportConfig = {
|
|
611
|
-
port: 3000,
|
|
612
|
-
host: '0.0.0.0',
|
|
613
|
-
corsOrigin: '*',
|
|
614
|
-
stateless: true,
|
|
615
|
-
}
|
|
616
|
-
const mockScheduler = { start: vi.fn(), stop: vi.fn() }
|
|
617
|
-
const transport = new HttpTransport(config)
|
|
618
|
-
await transport.start(
|
|
619
|
-
mockServer,
|
|
620
|
-
mockScheduler as unknown as Parameters<HttpTransport['start']>[1]
|
|
621
|
-
)
|
|
622
|
-
await transport.stop(mockScheduler as unknown as Parameters<HttpTransport['stop']>[0])
|
|
623
|
-
|
|
624
|
-
expect(mockScheduler.stop).toHaveBeenCalled()
|
|
625
|
-
})
|
|
626
|
-
})
|
|
627
|
-
|
|
628
|
-
// ========================================================================
|
|
629
|
-
// Auth middleware
|
|
630
|
-
// ========================================================================
|
|
631
|
-
|
|
632
|
-
describe('auth middleware', () => {
|
|
633
|
-
it('should reject bad tokens with 401', async () => {
|
|
634
|
-
const config: HttpTransportConfig = {
|
|
635
|
-
port: 3000,
|
|
636
|
-
host: '0.0.0.0',
|
|
637
|
-
corsOrigins: ['http://localhost'],
|
|
638
|
-
stateless: true,
|
|
639
|
-
authToken: 'secret-token',
|
|
640
|
-
}
|
|
641
|
-
const transport = new HttpTransport(config)
|
|
642
|
-
await transport.start(mockServer, null)
|
|
643
|
-
|
|
644
|
-
// Auth middleware checks req.path and authorization header.
|
|
645
|
-
// It's registered via app.use() after security, CORS, json, rate-limit.
|
|
646
|
-
// Find it by testing: sends 401 for bad token on /mcp path.
|
|
647
|
-
const authMw = mockMiddlewares.find((mw) => {
|
|
648
|
-
const req = mockReq({
|
|
649
|
-
path: '/mcp',
|
|
650
|
-
headers: { authorization: 'Bearer wrong-token' },
|
|
651
|
-
})
|
|
652
|
-
const res = mockRes()
|
|
653
|
-
mw(req, res, () => {})
|
|
654
|
-
return (res['status'] as ReturnType<typeof vi.fn>).mock.calls.some(
|
|
655
|
-
(c: unknown[]) => c[0] === 401
|
|
656
|
-
)
|
|
657
|
-
})
|
|
658
|
-
|
|
659
|
-
expect(authMw).toBeDefined()
|
|
660
|
-
|
|
661
|
-
// /health should bypass auth
|
|
662
|
-
const healthReq = mockReq({ path: '/health' })
|
|
663
|
-
const healthRes = mockRes()
|
|
664
|
-
let healthNext = false
|
|
665
|
-
authMw!(healthReq, healthRes, () => {
|
|
666
|
-
healthNext = true
|
|
667
|
-
})
|
|
668
|
-
expect(healthNext).toBe(true)
|
|
669
|
-
|
|
670
|
-
// Valid token should pass through
|
|
671
|
-
const goodReq = mockReq({
|
|
672
|
-
path: '/mcp',
|
|
673
|
-
headers: { authorization: 'Bearer secret-token' },
|
|
674
|
-
})
|
|
675
|
-
const goodRes = mockRes()
|
|
676
|
-
let goodNext = false
|
|
677
|
-
authMw!(goodReq, goodRes, () => {
|
|
678
|
-
goodNext = true
|
|
679
|
-
})
|
|
680
|
-
expect(goodNext).toBe(true)
|
|
681
|
-
})
|
|
682
|
-
})
|
|
683
|
-
|
|
684
|
-
// ========================================================================
|
|
685
|
-
// OAuth mode middleware
|
|
686
|
-
// ========================================================================
|
|
687
|
-
|
|
688
|
-
describe('oauth mode setups', () => {
|
|
689
|
-
it('should setup OAuth 2.1 authentication when enabled', async () => {
|
|
690
|
-
const config: HttpTransportConfig = {
|
|
691
|
-
port: 3000,
|
|
692
|
-
host: '0.0.0.0',
|
|
693
|
-
corsOrigins: ['*'],
|
|
694
|
-
stateless: true,
|
|
695
|
-
oauthEnabled: true,
|
|
696
|
-
oauthIssuer: 'https://auth.example.com',
|
|
697
|
-
oauthAudience: 'test-audience',
|
|
698
|
-
}
|
|
699
|
-
const transport = new HttpTransport(config)
|
|
700
|
-
await transport.start(mockServer, null)
|
|
701
|
-
|
|
702
|
-
// Should register the well-known route
|
|
703
|
-
expect(mockRoutes['get']!['/.well-known/oauth-protected-resource']).toBeDefined()
|
|
704
|
-
})
|
|
705
|
-
|
|
706
|
-
it('should enforce OAuth scopes on tools/call requests', async () => {
|
|
707
|
-
const config: HttpTransportConfig = {
|
|
708
|
-
port: 3000,
|
|
709
|
-
host: '0.0.0.0',
|
|
710
|
-
corsOrigins: ['*'],
|
|
711
|
-
stateless: true,
|
|
712
|
-
oauthEnabled: true,
|
|
713
|
-
oauthIssuer: 'https://auth.example.com',
|
|
714
|
-
oauthAudience: 'test-audience',
|
|
715
|
-
}
|
|
716
|
-
const transport = new HttpTransport(config)
|
|
717
|
-
await transport.start(mockServer, null)
|
|
718
|
-
|
|
719
|
-
// The OAuth scope middleware is pushed into mockMiddlewares.
|
|
720
|
-
// We can find it by passing a request with no `req.auth` and a tool call body, which returns 401.
|
|
721
|
-
let scopeMw: Function | undefined
|
|
722
|
-
for (const mw of mockMiddlewares) {
|
|
723
|
-
const req = mockReq({
|
|
724
|
-
method: 'POST',
|
|
725
|
-
body: { method: 'tools/call', params: { name: 'mj_execute_code' } },
|
|
726
|
-
})
|
|
727
|
-
const res = mockRes()
|
|
728
|
-
try {
|
|
729
|
-
mw(req, res, () => {})
|
|
730
|
-
} catch {}
|
|
731
|
-
if ((res['status'] as any).mock.calls.some((c: any) => c[0] === 401)) {
|
|
732
|
-
scopeMw = mw
|
|
733
|
-
break
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
expect(scopeMw).toBeDefined()
|
|
737
|
-
|
|
738
|
-
// Test non-POST / non-tools/call
|
|
739
|
-
const req1 = mockReq({ method: 'GET' })
|
|
740
|
-
const next1 = vi.fn()
|
|
741
|
-
scopeMw!(req1, mockRes(), next1)
|
|
742
|
-
expect(next1).toHaveBeenCalled()
|
|
743
|
-
|
|
744
|
-
// Test no params name
|
|
745
|
-
const req2 = mockReq({ method: 'POST', body: { method: 'tools/call' } })
|
|
746
|
-
const next2 = vi.fn()
|
|
747
|
-
scopeMw!(req2, mockRes(), next2)
|
|
748
|
-
expect(next2).toHaveBeenCalled()
|
|
749
|
-
|
|
750
|
-
// Test sufficient scope
|
|
751
|
-
const req3 = mockReq({
|
|
752
|
-
method: 'POST',
|
|
753
|
-
body: { method: 'tools/call', params: { name: 'mj_execute_code' } },
|
|
754
|
-
auth: { scopes: ['admin'] }, // codemode requires admin scope
|
|
755
|
-
})
|
|
756
|
-
const next3 = vi.fn()
|
|
757
|
-
scopeMw!(req3, mockRes(), next3)
|
|
758
|
-
expect(next3).toHaveBeenCalled()
|
|
759
|
-
|
|
760
|
-
// Test insufficient scope
|
|
761
|
-
const req4 = mockReq({
|
|
762
|
-
method: 'POST',
|
|
763
|
-
body: { method: 'tools/call', params: { name: 'mj_execute_code' } },
|
|
764
|
-
auth: { scopes: ['read'] },
|
|
765
|
-
})
|
|
766
|
-
const res4 = mockRes()
|
|
767
|
-
scopeMw!(req4, res4, vi.fn())
|
|
768
|
-
expect(res4['status']).toHaveBeenCalledWith(403)
|
|
769
|
-
})
|
|
770
|
-
})
|
|
771
|
-
})
|