[mcp] update audit events for streamable HTTP transport (#59155)

* [mcp] update audit events for streamable HTTP transport

* nolint for unused functions for now, they will be used in next PR
This commit is contained in:
STeve (Xin) Huang
2025-09-17 21:52:29 +00:00
committed by GitHub
parent 3f78d8f1b8
commit b4c6e8c268
15 changed files with 2869 additions and 1297 deletions
@@ -4885,6 +4885,8 @@ message OneOf {
events.BoundKeypairRecovery BoundKeypairRecovery = 220;
events.BoundKeypairRotation BoundKeypairRotation = 221;
events.BoundKeypairJoinStateVerificationFailed BoundKeypairJoinStateVerificationFailed = 222;
events.MCPSessionListenSSEStream MCPSessionListenSSEStream = 223;
events.MCPSessionInvalidHTTPRequest MCPSessionInvalidHTTPRequest = 224;
}
}
@@ -8714,6 +8716,16 @@ message MCPSessionEnd {
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Status contains common command or operation status fields.
//
// The protocol spec states that the MCP server may refuse the clients from
// terminating the session.
// https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#session-management
Status Status = 7 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
}
// MCPJSONRPCMessage includes details of a MCP request or notification.
@@ -8803,6 +8815,92 @@ message MCPSessionNotification {
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "message,omitempty"
];
// Status contains information whether the request is successful or not.
Status status = 6 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
}
// MCPSessionListenSSEStream is emitted when client sends a GET request to a
// streamable HTTP MCP server for listening server notifications via a SSE
// stream.
message MCPSessionListenSSEStream {
// Metadata is a common event metadata
Metadata metadata = 1 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// User is a common user event metadata
UserMetadata user = 2 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// SessionMetadata is a common event session metadata
SessionMetadata session = 3 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// App is a common application resource metadata.
AppMetadata app = 4 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Status contains information whether the request is successful or not.
Status status = 5 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
}
// MCPSessionInvalidHTTPRequest is a blanket event for all requests that we do
// not understand (usually out of MCP spec).
message MCPSessionInvalidHTTPRequest {
// Metadata is a common event metadata
Metadata metadata = 1 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// User is a common user event metadata
UserMetadata user = 2 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// SessionMetadata is a common event session metadata
SessionMetadata session = 3 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// App is a common application resource metadata.
AppMetadata app = 4 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Path is relative path in the URL.
string path = 5 [(gogoproto.jsontag) = "path"];
// Method is the request HTTP method, like GET/POST/DELETE/etc.
string method = 6 [(gogoproto.jsontag) = "method"];
// RawQuery are the encoded query values.
string raw_query = 7 [(gogoproto.jsontag) = "raw_query"];
// Body is the request HTTP body.
bytes body = 8 [(gogoproto.jsontag) = "body"];
// Headers are the HTTP request headers.
wrappers.LabelValues headers = 9 [
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "headers,omitempty",
(gogoproto.customtype) = "github.com/gravitational/teleport/api/types/wrappers.Traits"
];
}
// BoundKeypairRecovery is emitted when a client performs a self recovery using
+31
View File
@@ -2601,6 +2601,37 @@ func (m *MCPSessionNotification) TrimToMaxSize(maxSize int) AuditEvent {
return out
}
func (m *MCPSessionListenSSEStream) TrimToMaxSize(maxSize int) AuditEvent {
return m
}
func (m *MCPSessionInvalidHTTPRequest) TrimToMaxSize(maxSize int) AuditEvent {
size := m.Size()
if size <= maxSize {
return m
}
out := utils.CloneProtoMsg(m)
out.Path = ""
out.RawQuery = ""
out.Headers = nil
out.Body = nil
maxSize = adjustedMaxSize(out, maxSize)
customFieldsCount := nonEmptyStrs(m.Path, m.RawQuery) + nonEmptyTraits(m.Headers)
if len(m.Body) != 0 {
customFieldsCount++
}
maxFieldsSize := maxSizePerField(maxSize, customFieldsCount)
out.Path = trimStr(m.Path, maxFieldsSize)
out.RawQuery = trimStr(m.RawQuery, maxFieldsSize)
out.Body = []byte(trimStr(string(m.Body), maxFieldsSize))
out.Headers = trimTraits(m.Headers, maxFieldsSize)
return out
}
func (m *BoundKeypairRecovery) TrimToMaxSize(maxSize int) AuditEvent {
size := m.Size()
if size <= maxSize {
+2478 -1284
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -902,6 +902,14 @@ func ToOneOf(in AuditEvent) (*OneOf, error) {
out.Event = &OneOf_MCPSessionNotification{
MCPSessionNotification: e,
}
case *MCPSessionListenSSEStream:
out.Event = &OneOf_MCPSessionListenSSEStream{
MCPSessionListenSSEStream: e,
}
case *MCPSessionInvalidHTTPRequest:
out.Event = &OneOf_MCPSessionInvalidHTTPRequest{
MCPSessionInvalidHTTPRequest: e,
}
case *BoundKeypairRecovery:
out.Event = &OneOf_BoundKeypairRecovery{
BoundKeypairRecovery: e,
+6
View File
@@ -927,6 +927,12 @@ const (
// MCPSessionNotificationEvent is emitted when a notification is sent by
// client during a MCP session.
MCPSessionNotificationEvent = "mcp.session.notification"
// MCPSessionListenSSEStream is emitted when the client sends a GET request for
// listening server notifications via an SSE stream.
MCPSessionListenSSEStream = "mcp.session.listen_sse_stream"
// MCPSessionInvalidHTTPRequest is a blanket event for all requests that we
// do not understand (usually out of MCP spec).
MCPSessionInvalidHTTPRequest = "mcp.session.invalid_http_request"
// BoundKeypairRecovery is emitted when a bound keypair token is used to
// perform a recovery.
+18 -1
View File
@@ -743,14 +743,31 @@ const (
MCPSessionStartCode = "TMCP001I"
// MCPSessionEndCode is the event code for mcp.session.end.
MCPSessionEndCode = "TMCP002I"
// MCPSessionEndFailureCode is the event code for mcp.session.end when the
// end request is denied by the MCP server.
MCPSessionEndFailureCode = "TMCP002E"
// MCPSessionRequestCode is the event code for mcp.session.request.
MCPSessionRequestCode = "TMCP003I"
// MCPSessionRequestFailureCode is the event code for mcp.session.request
// when the request is denied by Teleport.
// when the request is denied by Teleport or the MCP server.
MCPSessionRequestFailureCode = "TMCP003E"
// MCPSessionNotificationCode is the event code for
// mcp.session.notification.
MCPSessionNotificationCode = "TMCP004I"
// MCPSessionNotificationFailureCode is the event code for
// mcp.session.notification when the notification is denied by the MCP
// server.
MCPSessionNotificationFailureCode = "TMCP004E"
// MCPSessionListenSSEStreamCode is the event code for
// mcp.session.listen_sse_stream.
MCPSessionListenSSEStreamCode = "TMCP005I"
// MCPSessionListenSSEStreamFailureCode is the event code for
// mcp.session.listen_sse_stream when the request is denied by the MCP
// server.
MCPSessionListenSSEStreamFailureCode = "TMCP005E"
// MCPSessionInvalidHTTPRequestCode is the event code for
// mcp.session.invalid_http_request.
MCPSessionInvalidHTTPRequestCode = "TMCP006E"
// BoundKeypairRecoveryCode is the event code for
// join_token.bound_keypair.recovery.
+4
View File
@@ -532,6 +532,10 @@ func FromEventFields(fields EventFields) (events.AuditEvent, error) {
e = &events.MCPSessionRequest{}
case MCPSessionNotificationEvent:
e = &events.MCPSessionNotification{}
case MCPSessionListenSSEStream:
e = &events.MCPSessionListenSSEStream{}
case MCPSessionInvalidHTTPRequest:
e = &events.MCPSessionInvalidHTTPRequest{}
case BoundKeypairRecovery:
e = &events.BoundKeypairRecovery{}
+68 -7
View File
@@ -21,6 +21,7 @@ package mcp
import (
"context"
"log/slog"
"net/http"
"github.com/gravitational/trace"
"github.com/mark3labs/mcp-go/mcp"
@@ -29,6 +30,7 @@ import (
apidefaults "github.com/gravitational/teleport/api/defaults"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/mcputils"
)
@@ -103,8 +105,8 @@ func (a *sessionAuditor) emitStartEvent(ctx context.Context) {
})
}
func (a *sessionAuditor) emitEndEvent(ctx context.Context) {
a.emitEvent(ctx, &apievents.MCPSessionEnd{
func (a *sessionAuditor) emitEndEvent(ctx context.Context, err error) {
event := &apievents.MCPSessionEnd{
Metadata: a.makeEventMetadata(
events.MCPSessionEndEvent,
events.MCPSessionEndCode,
@@ -114,14 +116,23 @@ func (a *sessionAuditor) emitEndEvent(ctx context.Context) {
UserMetadata: a.makeUserMetadata(),
ConnectionMetadata: a.makeConnectionMetadata(),
AppMetadata: a.makeAppMetadata(),
})
Status: apievents.Status{
Success: true,
},
}
if err != nil {
event.Metadata.Code = events.MCPSessionEndFailureCode
event.Status.Success = false
event.Status.Error = err.Error()
}
a.emitEvent(ctx, event)
}
func (a *sessionAuditor) emitNotificationEvent(ctx context.Context, msg *mcputils.JSONRPCNotification) {
if !a.shouldEmitEvent(msg.Method) {
func (a *sessionAuditor) emitNotificationEvent(ctx context.Context, msg *mcputils.JSONRPCNotification, err error) {
if err == nil && !a.shouldEmitEvent(msg.Method) {
return
}
a.emitEvent(ctx, &apievents.MCPSessionNotification{
event := &apievents.MCPSessionNotification{
Metadata: a.makeEventMetadata(
events.MCPSessionNotificationEvent,
events.MCPSessionNotificationCode,
@@ -134,7 +145,16 @@ func (a *sessionAuditor) emitNotificationEvent(ctx context.Context, msg *mcputil
Method: string(msg.Method),
Params: msg.Params.GetEventParams(),
},
})
Status: apievents.Status{
Success: true,
},
}
if err != nil {
event.Metadata.Code = events.MCPSessionNotificationFailureCode
event.Status.Success = false
event.Status.Error = err.Error()
}
a.emitEvent(ctx, event)
}
func (a *sessionAuditor) emitRequestEvent(ctx context.Context, msg *mcputils.JSONRPCRequest, err error) {
@@ -168,6 +188,47 @@ func (a *sessionAuditor) emitRequestEvent(ctx context.Context, msg *mcputils.JSO
a.emitEvent(ctx, event)
}
//nolint:unused //TODO(greedy52) remove nolint
func (a *sessionAuditor) emitListenSSEStreamEvent(ctx context.Context, err error) {
event := &apievents.MCPSessionListenSSEStream{
Metadata: a.makeEventMetadata(
events.MCPSessionListenSSEStream,
events.MCPSessionListenSSEStreamCode,
),
SessionMetadata: a.makeSessionMetadata(),
UserMetadata: a.makeUserMetadata(),
AppMetadata: a.makeAppMetadata(),
Status: apievents.Status{
Success: true,
},
}
if err != nil {
event.Metadata.Code = events.MCPSessionListenSSEStreamFailureCode
event.Status.Success = false
event.Status.Error = err.Error()
}
a.emitEvent(ctx, event)
}
//nolint:unused //TODO(greedy52) remove nolint
func (a *sessionAuditor) emitInvalidHTTPRequest(ctx context.Context, r *http.Request) {
body, _ := utils.GetAndReplaceRequestBody(r)
event := &apievents.MCPSessionInvalidHTTPRequest{
Metadata: a.makeEventMetadata(
events.MCPSessionInvalidHTTPRequest,
events.MCPSessionInvalidHTTPRequestCode,
),
SessionMetadata: a.makeSessionMetadata(),
UserMetadata: a.makeUserMetadata(),
AppMetadata: a.makeAppMetadata(),
Path: r.URL.Path,
Method: r.Method,
Body: body,
RawQuery: r.URL.RawQuery,
}
a.emitEvent(ctx, event)
}
func (a *sessionAuditor) emitEvent(ctx context.Context, event apievents.AuditEvent) {
preparedEvent, err := a.preparer.PrepareSessionEvent(event)
if err != nil {
+1 -1
View File
@@ -176,7 +176,7 @@ func (s *sessionHandler) checkAccessToTool(ctx context.Context, toolName string)
}
func (s *sessionHandler) processClientNotification(ctx context.Context, notification *mcputils.JSONRPCNotification) {
s.emitNotificationEvent(ctx, notification)
s.emitNotificationEvent(ctx, notification, nil)
}
func (s *sessionHandler) onClientNotification(serverRequestWriter mcputils.MessageWriter) mcputils.HandleNotificationFunc {
+1 -1
View File
@@ -102,7 +102,7 @@ func (s *Server) handleStdioToSSE(ctx context.Context, sessionCtx *SessionCtx) e
// TODO(greedy52) capture client info then emit start event with client
// information.
session.emitStartEvent(s.cfg.ParentContext)
defer session.emitEndEvent(s.cfg.ParentContext)
defer session.emitEndEvent(s.cfg.ParentContext, nil)
// Wait until reader finishes.
clientRequestReader.Run(ctx)
+1 -1
View File
@@ -135,7 +135,7 @@ func (s *Server) handleStdio(ctx context.Context, sessionCtx *SessionCtx, makeSe
// TODO(greedy52) capture client info then emit start event with client
// information.
session.emitStartEvent(s.cfg.ParentContext)
defer session.emitEndEvent(s.cfg.ParentContext)
defer session.emitEndEvent(s.cfg.ParentContext, nil)
go clientRequestReader.Run(ctx)
go serverResponseReader.Run(ctx)
@@ -316,9 +316,14 @@ const EventIconMap: Record<EventCode, any> = {
[eventCodes.AUTOUPDATE_AGENT_ROLLOUT_ROLLBACK]: Icons.Restore,
[eventCodes.MCP_SESSION_START]: Icons.ModelContextProtocol,
[eventCodes.MCP_SESSION_END]: Icons.ModelContextProtocol,
[eventCodes.MCP_SESSION_END_FAILURE]: Icons.Warning,
[eventCodes.MCP_SESSION_REQUEST]: Icons.ModelContextProtocol,
[eventCodes.MCP_SESSION_REQUEST_FAILURE]: Icons.Warning,
[eventCodes.MCP_SESSION_NOTIFICATION]: Icons.ModelContextProtocol,
[eventCodes.MCP_SESSION_NOTIFICATION_FAILURE]: Icons.Warning,
[eventCodes.MCP_SESSION_LISTEN_SSE_STREAM]: Icons.ModelContextProtocol,
[eventCodes.MCP_SESSION_LISTEN_SSE_STREAM_FAILURE]: Icons.Warning,
[eventCodes.MCP_SESSION_INVALID_HTTP_REQUEST]: Icons.Warning,
[eventCodes.BOUND_KEYPAIR_RECOVERY]: Icons.Info,
[eventCodes.BOUND_KEYPAIR_ROTATION]: Icons.Info,
[eventCodes.BOUND_KEYPAIR_JOIN_STATE_VERIFICATION_FAILED]: Icons.Warning,
@@ -4094,6 +4094,24 @@ export const events = [
jsonrpc: '2.0',
},
},
{
code: 'TMCP004E',
ei: 0,
event: 'mcp.session.notification',
namespace: 'default',
server_id: 'a0518380-0d53-4188-ac8b-8ddd8103e45b',
sid: '6593cf87-9839-4f18-abf8-c54873aaeb4e',
time: '2025-05-23T11:11:11.333Z',
uid: '80400ed9-644e-4a6e-ab99-b264b34d0f55',
user: 'ai-user',
app_name: 'mcp-everything',
success: false,
message: {
method: 'notifications/initialized',
jsonrpc: '2.0',
},
error: 'HTTP 401 Unauthorized',
},
{
code: 'TMCP003I',
ei: 0,
@@ -4154,6 +4172,62 @@ export const events = [
uid: '80400ed9-644e-4a6e-ab99-b264b34d0f55',
user: 'ai-user',
app_name: 'mcp-everything',
success: true,
},
{
code: 'TMCP002E',
ei: 0,
event: 'mcp.session.end',
namespace: 'default',
server_id: 'a0518380-0d53-4188-ac8b-8ddd8103e45b',
sid: '6593cf87-9839-4f18-abf8-c54873aaeb4e',
time: '2025-05-23T12:22:22.222Z',
uid: '80400ed9-644e-4a6e-ab99-b264b34d0f55',
user: 'ai-user',
app_name: 'mcp-everything',
success: true,
error: 'HTTP 405 Method Not Allowed',
},
{
code: 'TMCP005I',
ei: 0,
event: 'mcp.session.listen_sse_stream',
namespace: 'default',
server_id: 'a0518380-0d53-4188-ac8b-8ddd8103e45b',
sid: '6593cf87-9839-4f18-abf8-c54873aaeb4e',
time: '2025-05-23T12:22:22.222Z',
uid: '80400ed9-644e-4a6e-ab99-b264b34d0f55',
user: 'ai-user',
app_name: 'mcp-everything',
success: true,
},
{
code: 'TMCP005E',
ei: 0,
event: 'mcp.session.listen_sse_stream',
namespace: 'default',
server_id: 'a0518380-0d53-4188-ac8b-8ddd8103e45b',
sid: '6593cf87-9839-4f18-abf8-c54873aaeb4e',
time: '2025-05-23T12:22:22.222Z',
uid: '80400ed9-644e-4a6e-ab99-b264b34d0f55',
user: 'ai-user',
app_name: 'mcp-everything',
success: false,
error: 'HTTP 405 Method Not Allowed',
},
{
code: 'TMCP006E',
ei: 0,
event: 'mcp.session.invalid_http_request',
namespace: 'default',
server_id: 'a0518380-0d53-4188-ac8b-8ddd8103e45b',
sid: '6593cf87-9839-4f18-abf8-c54873aaeb4e',
time: '2025-05-23T12:22:22.222Z',
uid: '80400ed9-644e-4a6e-ab99-b264b34d0f55',
user: 'ai-user',
app_name: 'mcp-everything',
method: 'OPTIONS',
path: '/',
},
].map(makeEvent);
@@ -2253,6 +2253,14 @@ export const formatters: Formatters = {
return `User [${user}] has disconnected from MCP server [${app_name}]`;
},
},
[eventCodes.MCP_SESSION_END_FAILURE]: {
type: 'mcp.session.end',
desc: 'MCP Session End Failure',
format: event => {
const { user, app_name } = event;
return `User [${user}] failed to disconnect from MCP server [${app_name}]`;
},
},
[eventCodes.MCP_SESSION_REQUEST]: {
type: 'mcp.session.request',
desc: 'MCP Session Request',
@@ -2268,9 +2276,9 @@ export const formatters: Formatters = {
desc: 'MCP Session Request Failure',
format: ({ user, app_name, message }) => {
if (message.params?.name) {
return `User [${user}] was denied access to an MCP request [${message.method}] for [${message.params.name}] to MCP server [${app_name}]`;
return `User [${user}] failed to send an MCP request [${message.method}] for [${message.params.name}] to MCP server [${app_name}]`;
}
return `User [${user}] was denied access to an MCP request [${message.method}] to MCP server [${app_name}]`;
return `User [${user}] failed to send an MCP request [${message.method}] to MCP server [${app_name}]`;
},
},
[eventCodes.MCP_SESSION_NOTIFICATION]: {
@@ -2280,6 +2288,34 @@ export const formatters: Formatters = {
return `User [${user}] sent an MCP notification [${message.method}] to MCP server [${app_name}]`;
},
},
[eventCodes.MCP_SESSION_NOTIFICATION_FAILURE]: {
type: 'mcp.session.notification',
desc: 'MCP Session Notification Failure',
format: ({ user, app_name, message }) => {
return `User [${user}] failed to send an MCP notification [${message.method}] to MCP server [${app_name}]`;
},
},
[eventCodes.MCP_SESSION_LISTEN_SSE_STREAM]: {
type: 'mcp.session.listen_sse_stream',
desc: 'MCP Session Listen',
format: ({ user, app_name }) => {
return `User [${user}] has started listening events from MCP server [${app_name}]`;
},
},
[eventCodes.MCP_SESSION_LISTEN_SSE_STREAM_FAILURE]: {
type: 'mcp.session.listen_sse_stream',
desc: 'MCP Session Listen Failure',
format: ({ user, app_name }) => {
return `User [${user}] failed to listen events from MCP server [${app_name}]`;
},
},
[eventCodes.MCP_SESSION_INVALID_HTTP_REQUEST]: {
type: 'mcp.session.invalid_http_request',
desc: 'MCP Session Invalid Request',
format: ({ user, app_name }) => {
return `User [${user}] attempted to send an invalid request to MCP server [${app_name}]`;
},
},
[eventCodes.BOUND_KEYPAIR_RECOVERY]: {
type: 'join_token.bound_keypair.recovery',
desc: 'Bound Keypair Recovery',
@@ -335,9 +335,14 @@ export const eventCodes = {
AUTOUPDATE_AGENT_ROLLOUT_ROLLBACK: 'AUAR003I',
MCP_SESSION_START: 'TMCP001I',
MCP_SESSION_END: 'TMCP002I',
MCP_SESSION_END_FAILURE: 'TMCP002E',
MCP_SESSION_REQUEST: 'TMCP003I',
MCP_SESSION_REQUEST_FAILURE: 'TMCP003E',
MCP_SESSION_NOTIFICATION: 'TMCP004I',
MCP_SESSION_NOTIFICATION_FAILURE: 'TMCP004E',
MCP_SESSION_LISTEN_SSE_STREAM: 'TMCP005I',
MCP_SESSION_LISTEN_SSE_STREAM_FAILURE: 'TMCP005E',
MCP_SESSION_INVALID_HTTP_REQUEST: 'TMCP006E',
BOUND_KEYPAIR_RECOVERY: 'TBK001I',
BOUND_KEYPAIR_ROTATION: 'TBK002I',
BOUND_KEYPAIR_JOIN_STATE_VERIFICATION_FAILED: 'TBK003W',
@@ -1956,6 +1961,12 @@ export type RawEvents = {
app_name: string;
}
>;
[eventCodes.MCP_SESSION_END_FAILURE]: RawEvent<
typeof eventCodes.MCP_SESSION_END_FAILURE,
{
app_name: string;
}
>;
[eventCodes.MCP_SESSION_REQUEST]: RawEvent<
typeof eventCodes.MCP_SESSION_REQUEST,
{
@@ -1989,6 +2000,33 @@ export type RawEvents = {
};
}
>;
[eventCodes.MCP_SESSION_NOTIFICATION_FAILURE]: RawEvent<
typeof eventCodes.MCP_SESSION_NOTIFICATION_FAILURE,
{
app_name: string;
message: {
method: string;
};
}
>;
[eventCodes.MCP_SESSION_LISTEN_SSE_STREAM]: RawEvent<
typeof eventCodes.MCP_SESSION_LISTEN_SSE_STREAM,
{
app_name: string;
}
>;
[eventCodes.MCP_SESSION_LISTEN_SSE_STREAM_FAILURE]: RawEvent<
typeof eventCodes.MCP_SESSION_LISTEN_SSE_STREAM_FAILURE,
{
app_name: string;
}
>;
[eventCodes.MCP_SESSION_INVALID_HTTP_REQUEST]: RawEvent<
typeof eventCodes.MCP_SESSION_INVALID_HTTP_REQUEST,
{
app_name: string;
}
>;
[eventCodes.BOUND_KEYPAIR_RECOVERY]: RawEvent<
typeof eventCodes.BOUND_KEYPAIR_RECOVERY,
{