Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,10 @@ A client-requested `ttl` must be a non-negative integer number of milliseconds;
`CreateTaskResult`.

`taskDefaultTtlMs`, `taskMaxTtlMs` and `taskLeaseMs` must be positive integer numbers of
milliseconds and `taskMaxRetainedPerPrincipal` a positive integer. Registration throws a
`TypeError` naming the option otherwise, so a typo or a value read from an environment variable
as a string fails at startup rather than failing every task. A `taskDefaultTtlMs` above
`taskMaxTtlMs` is capped to it.
milliseconds, `taskMaxRetainedPerPrincipal` a positive integer, and `taskDefaultTtlMs` must not
exceed `taskMaxTtlMs` (default 1 hour). Registration throws a `TypeError` naming the option
otherwise, so a typo or a value read from an environment variable as a string fails at startup
rather than failing every task.

Tasks are stored in memory by default and in Redis when a `redis` option is given, so any
instance can serve a poll for a task created on another.
Expand Down
2 changes: 1 addition & 1 deletion src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ const DEFAULT_POLL_INTERVAL = 1000
const DEFAULT_TASK_TTL = 60_000

/** Ceiling on retention, so a client cannot pin resources indefinitely */
const MAX_TASK_TTL = 3600_000
export const MAX_TASK_TTL = 3600_000

/**
* Floor on retention. A smaller ttl (0 included) would let the task expire
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import oauthClientPlugin from './auth/oauth-client.ts'
import authRoutesPlugin from './routes/auth-routes.ts'
import { quitWithTimeout } from './redis-quit-with-timeout.ts'
import { createJsonSchemaValidator } from './validation/json-schema-validator.ts'
import { MAX_TASK_TTL } from './handlers.ts'
import {
createMcpClient,
type McpClient,
Expand Down Expand Up @@ -110,6 +111,11 @@ const mcpPlugin = fp(async function (app: FastifyInstance, opts: MCPPluginOption
throw new TypeError(`${name} must be a positive integer number of milliseconds, got ${String(value)}`)
}
}
// An unset default is capped by the ceiling, but an explicit one above it is a mistake
const taskMaxTtlMs = opts.taskMaxTtlMs ?? MAX_TASK_TTL
if (opts.taskDefaultTtlMs !== undefined && opts.taskDefaultTtlMs > taskMaxTtlMs) {
throw new TypeError(`taskDefaultTtlMs (${opts.taskDefaultTtlMs}) must not exceed taskMaxTtlMs (${taskMaxTtlMs})`)
}
const tools = new Map<string, MCPTool>()
const resources = new Map<string, MCPResource>()
const prompts = new Map<string, MCPPrompt>()
Expand Down
9 changes: 6 additions & 3 deletions test/task-options-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ describe('task options validation (#302)', () => {
await assertRegistrationAccepts(t, { taskMaxRetainedPerPrincipal: 20 })
})

test('registration accepts a taskDefaultTtlMs above taskMaxTtlMs, which is capped', async (t: TestContext) => {
await assertRegistrationAccepts(t, { taskDefaultTtlMs: 120_000, taskMaxTtlMs: 60_000 })
await assertRegistrationAccepts(t, { taskDefaultTtlMs: 2 * 3600_000 })
test('registration throws when taskDefaultTtlMs exceeds taskMaxTtlMs', async (t: TestContext) => {
await assertRegistrationRejects(t, { taskDefaultTtlMs: 120_000, taskMaxTtlMs: 60_000 }, /taskDefaultTtlMs \(120000\) must not exceed taskMaxTtlMs \(60000\)/)
})

test('registration throws when taskDefaultTtlMs exceeds the default taskMaxTtlMs', async (t: TestContext) => {
await assertRegistrationRejects(t, { taskDefaultTtlMs: 2 * 3600_000 }, /taskDefaultTtlMs \(7200000\) must not exceed taskMaxTtlMs \(3600000\)/)
})

test('registration accepts taskDefaultTtlMs equal to taskMaxTtlMs', async (t: TestContext) => {
Expand Down
Loading