Skip to content
Merged
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
47 changes: 34 additions & 13 deletions scripts/orbit-slice1-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,26 @@ let firstCredentialToken = '';
assert.equal(privateStateBody.recordCounts.total, 0);
assert.equal(privateStateBody.recordCounts.pendingReview, 0);

const newerConcurrentUse = await postJson(
`/v1/mcp/grants/${encodeURIComponent(mcpGrantId)}/agent/state`,
{},
{ authorization: `Bearer ${MCP_SERVICE_SECRET}` },
NOW + 57,
);
assert.equal(newerConcurrentUse.status, 200, await newerConcurrentUse.clone().text());

const olderConcurrentUse = await postJson(
`/v1/mcp/grants/${encodeURIComponent(mcpGrantId)}/resolve`,
{},
{ authorization: `Bearer ${MCP_SERVICE_SECRET}` },
NOW + 56,
);
assert.equal(olderConcurrentUse.status, 200, await olderConcurrentUse.clone().text());
const olderConcurrentUseBody = await olderConcurrentUse.json() as {
authorization: { lastUsedAt: number };
};
assert.equal(olderConcurrentUseBody.authorization.lastUsedAt, NOW + 57);

await postJson('/__test/set-agent-status', {
handle: 'selene-test-agent',
status: 'suspended',
Expand Down Expand Up @@ -1080,21 +1100,22 @@ let firstCredentialToken = '';
assert.equal(sentBoxBody.directMessages[0]?.bodyMarkdown, directMessageBody.bodyMarkdown);
assert.equal(sentBoxBody.nextCursor, null);

const recipientUnread = await postJson(
`/v1/mcp/grants/${encodeURIComponent(recipientGrant.grantId)}/direct-messages/unread-count`,
{},
mcpServiceHeaders(),
NOW + 23_002,
);
const [recipientUnread, recipientInbox] = await Promise.all([
postJson(
`/v1/mcp/grants/${encodeURIComponent(recipientGrant.grantId)}/direct-messages/unread-count`,
{},
mcpServiceHeaders(),
NOW + 23_002,
),
postJson(
`/v1/mcp/grants/${encodeURIComponent(recipientGrant.grantId)}/direct-messages/list`,
{ box: 'inbox', limit: 10 },
mcpServiceHeaders(),
NOW + 23_003,
),
]);
assert.equal(recipientUnread.status, 200, await recipientUnread.clone().text());
assert.deepEqual(await recipientUnread.json(), { unreadCount: 1 });

const recipientInbox = await postJson(
`/v1/mcp/grants/${encodeURIComponent(recipientGrant.grantId)}/direct-messages/list`,
{ box: 'inbox', limit: 10 },
mcpServiceHeaders(),
NOW + 23_003,
);
assert.equal(recipientInbox.status, 200, await recipientInbox.clone().text());
const recipientInboxBody = await recipientInbox.json() as {
directMessages: Array<{ id: string; sender: { handle: string }; bodyMarkdown: string; readAt: number | null }>;
Expand Down
30 changes: 22 additions & 8 deletions src/server/http/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3205,15 +3205,29 @@ async function resolveActiveMcpGrant(
{ 'www-authenticate': 'Bearer' },
);
}
if (touch && !await mcpRepository.touchGrant({ grantId, usedAt: now })) {
if (grant.lastUsedAt !== now) {
throw new ApiError(
401,
'mcp_authorization_invalid',
'The Orbit MCP authorization changed before it could be used.',
{},
{ 'www-authenticate': 'Bearer' },
if (touch) {
const touched = await mcpRepository.touchGrant({ grantId, usedAt: now });
if (!touched) {
// A concurrent valid request may have advanced lastUsedAt after this request read the grant.
// Revalidate the live authorization and accept only if usage already reached this request time.
const refreshed = await resolveActiveMcpGrant(
grantId,
identityRepository,
agentRepository,
mcpRepository,
now,
false,
);
if (refreshed.grant.lastUsedAt === null || refreshed.grant.lastUsedAt < now) {
throw new ApiError(
401,
'mcp_authorization_invalid',
'The Orbit MCP authorization changed before it could be used.',
{},
{ 'www-authenticate': 'Bearer' },
);
}
return refreshed;
}
}
return {
Expand Down