Compare commits

..

No commits in common. "main" and "dev" have entirely different histories.
main ... dev

5 changed files with 129 additions and 143 deletions

View file

@ -8,31 +8,29 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
if (! Schema::hasTable('mcp_api_requests')) { Schema::create('mcp_api_requests', function (Blueprint $table) {
Schema::create('mcp_api_requests', function (Blueprint $table) { $table->id();
$table->id(); $table->string('request_id', 32)->unique();
$table->string('request_id', 32)->unique(); $table->foreignId('workspace_id')->nullable()->constrained('workspaces')->nullOnDelete();
$table->foreignId('workspace_id')->nullable()->constrained('workspaces')->nullOnDelete(); $table->foreignId('api_key_id')->nullable()->constrained('api_keys')->nullOnDelete();
$table->foreignId('api_key_id')->nullable()->constrained('api_keys')->nullOnDelete(); $table->string('method', 10);
$table->string('method', 10); $table->string('path', 255);
$table->string('path', 255); $table->json('headers')->nullable();
$table->json('headers')->nullable(); $table->json('request_body')->nullable();
$table->json('request_body')->nullable(); $table->unsignedSmallInteger('response_status');
$table->unsignedSmallInteger('response_status'); $table->json('response_body')->nullable();
$table->json('response_body')->nullable(); $table->unsignedInteger('duration_ms')->default(0);
$table->unsignedInteger('duration_ms')->default(0); $table->string('server_id', 64)->nullable();
$table->string('server_id', 64)->nullable(); $table->string('tool_name', 128)->nullable();
$table->string('tool_name', 128)->nullable(); $table->text('error_message')->nullable();
$table->text('error_message')->nullable(); $table->string('ip_address', 45)->nullable();
$table->string('ip_address', 45)->nullable(); $table->timestamps();
$table->timestamps();
$table->index(['workspace_id', 'created_at']); $table->index(['workspace_id', 'created_at']);
$table->index(['server_id', 'tool_name']); $table->index(['server_id', 'tool_name']);
$table->index('created_at'); $table->index('created_at');
$table->index('response_status'); $table->index('response_status');
}); });
}
} }
public function down(): void public function down(): void

View file

@ -8,40 +8,36 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
if (! Schema::hasTable('mcp_tool_metrics')) { Schema::create('mcp_tool_metrics', function (Blueprint $table) {
Schema::create('mcp_tool_metrics', function (Blueprint $table) { $table->id();
$table->id(); $table->string('tool_name');
$table->string('tool_name'); $table->string('workspace_id')->nullable();
$table->string('workspace_id')->nullable(); $table->unsignedInteger('call_count')->default(0);
$table->unsignedInteger('call_count')->default(0); $table->unsignedInteger('error_count')->default(0);
$table->unsignedInteger('error_count')->default(0); $table->unsignedInteger('total_duration_ms')->default(0);
$table->unsignedInteger('total_duration_ms')->default(0); $table->unsignedInteger('min_duration_ms')->nullable();
$table->unsignedInteger('min_duration_ms')->nullable(); $table->unsignedInteger('max_duration_ms')->nullable();
$table->unsignedInteger('max_duration_ms')->nullable(); $table->date('date');
$table->date('date'); $table->timestamps();
$table->timestamps();
$table->unique(['tool_name', 'workspace_id', 'date']); $table->unique(['tool_name', 'workspace_id', 'date']);
$table->index(['date', 'tool_name']); $table->index(['date', 'tool_name']);
$table->index('workspace_id'); $table->index('workspace_id');
}); });
}
// Table for tracking tool combinations (tools used together in sessions) // Table for tracking tool combinations (tools used together in sessions)
if (! Schema::hasTable('mcp_tool_combinations')) { Schema::create('mcp_tool_combinations', function (Blueprint $table) {
Schema::create('mcp_tool_combinations', function (Blueprint $table) { $table->id();
$table->id(); $table->string('tool_a');
$table->string('tool_a'); $table->string('tool_b');
$table->string('tool_b'); $table->string('workspace_id')->nullable();
$table->string('workspace_id')->nullable(); $table->unsignedInteger('occurrence_count')->default(0);
$table->unsignedInteger('occurrence_count')->default(0); $table->date('date');
$table->date('date'); $table->timestamps();
$table->timestamps();
$table->unique(['tool_a', 'tool_b', 'workspace_id', 'date']); $table->unique(['tool_a', 'tool_b', 'workspace_id', 'date']);
$table->index(['date', 'occurrence_count']); $table->index(['date', 'occurrence_count']);
}); });
}
} }
public function down(): void public function down(): void

View file

@ -8,20 +8,18 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
if (! Schema::hasTable('mcp_usage_quotas')) { Schema::create('mcp_usage_quotas', function (Blueprint $table) {
Schema::create('mcp_usage_quotas', function (Blueprint $table) { $table->id();
$table->id(); $table->foreignId('workspace_id')->constrained('workspaces')->cascadeOnDelete();
$table->foreignId('workspace_id')->constrained('workspaces')->cascadeOnDelete(); $table->string('month', 7); // YYYY-MM format
$table->string('month', 7); // YYYY-MM format $table->unsignedBigInteger('tool_calls_count')->default(0);
$table->unsignedBigInteger('tool_calls_count')->default(0); $table->unsignedBigInteger('input_tokens')->default(0);
$table->unsignedBigInteger('input_tokens')->default(0); $table->unsignedBigInteger('output_tokens')->default(0);
$table->unsignedBigInteger('output_tokens')->default(0); $table->timestamps();
$table->timestamps();
$table->unique(['workspace_id', 'month']); $table->unique(['workspace_id', 'month']);
$table->index('month'); $table->index('month');
}); });
}
} }
public function down(): void public function down(): void

View file

@ -8,70 +8,66 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
if (! Schema::hasTable('mcp_audit_logs')) { Schema::create('mcp_audit_logs', function (Blueprint $table) {
Schema::create('mcp_audit_logs', function (Blueprint $table) { $table->id();
$table->id();
// Tool execution details // Tool execution details
$table->string('server_id')->index(); $table->string('server_id')->index();
$table->string('tool_name')->index(); $table->string('tool_name')->index();
$table->unsignedBigInteger('workspace_id')->nullable()->index(); $table->unsignedBigInteger('workspace_id')->nullable()->index();
$table->string('session_id')->nullable()->index(); $table->string('session_id')->nullable()->index();
// Input/output (stored as JSON, may be redacted) // Input/output (stored as JSON, may be redacted)
$table->json('input_params')->nullable(); $table->json('input_params')->nullable();
$table->json('output_summary')->nullable(); $table->json('output_summary')->nullable();
$table->boolean('success')->default(true); $table->boolean('success')->default(true);
$table->unsignedInteger('duration_ms')->nullable(); $table->unsignedInteger('duration_ms')->nullable();
$table->string('error_code')->nullable(); $table->string('error_code')->nullable();
$table->text('error_message')->nullable(); $table->text('error_message')->nullable();
// Actor information // Actor information
$table->string('actor_type')->nullable(); // user, api_key, system $table->string('actor_type')->nullable(); // user, api_key, system
$table->unsignedBigInteger('actor_id')->nullable(); $table->unsignedBigInteger('actor_id')->nullable();
$table->string('actor_ip', 45)->nullable(); // IPv4 or IPv6 $table->string('actor_ip', 45)->nullable(); // IPv4 or IPv6
// Sensitive tool flagging // Sensitive tool flagging
$table->boolean('is_sensitive')->default(false)->index(); $table->boolean('is_sensitive')->default(false)->index();
$table->string('sensitivity_reason')->nullable(); $table->string('sensitivity_reason')->nullable();
// Hash chain for tamper detection // Hash chain for tamper detection
$table->string('previous_hash', 64)->nullable(); // SHA-256 of previous entry $table->string('previous_hash', 64)->nullable(); // SHA-256 of previous entry
$table->string('entry_hash', 64)->index(); // SHA-256 of this entry $table->string('entry_hash', 64)->index(); // SHA-256 of this entry
// Agent context // Agent context
$table->string('agent_type')->nullable(); $table->string('agent_type')->nullable();
$table->string('plan_slug')->nullable(); $table->string('plan_slug')->nullable();
// Timestamps (immutable - no updated_at updates after creation) // Timestamps (immutable - no updated_at updates after creation)
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable(); $table->timestamp('updated_at')->nullable();
// Foreign key constraint // Foreign key constraint
$table->foreign('workspace_id') $table->foreign('workspace_id')
->references('id') ->references('id')
->on('workspaces') ->on('workspaces')
->nullOnDelete(); ->nullOnDelete();
// Composite indexes for common queries // Composite indexes for common queries
$table->index(['workspace_id', 'created_at']); $table->index(['workspace_id', 'created_at']);
$table->index(['tool_name', 'created_at']); $table->index(['tool_name', 'created_at']);
$table->index(['is_sensitive', 'created_at']); $table->index(['is_sensitive', 'created_at']);
$table->index(['actor_type', 'actor_id']); $table->index(['actor_type', 'actor_id']);
}); });
}
// Table for tracking sensitive tool definitions // Table for tracking sensitive tool definitions
if (! Schema::hasTable('mcp_sensitive_tools')) { Schema::create('mcp_sensitive_tools', function (Blueprint $table) {
Schema::create('mcp_sensitive_tools', function (Blueprint $table) { $table->id();
$table->id(); $table->string('tool_name')->unique();
$table->string('tool_name')->unique(); $table->string('reason');
$table->string('reason'); $table->json('redact_fields')->nullable(); // Fields to redact in audit logs
$table->json('redact_fields')->nullable(); // Fields to redact in audit logs $table->boolean('require_explicit_consent')->default(false);
$table->boolean('require_explicit_consent')->default(false); $table->timestamps();
$table->timestamps(); });
});
}
} }
public function down(): void public function down(): void

View file

@ -8,32 +8,30 @@ return new class extends Migration
{ {
public function up(): void public function up(): void
{ {
if (! Schema::hasTable('mcp_tool_versions')) { Schema::create('mcp_tool_versions', function (Blueprint $table) {
Schema::create('mcp_tool_versions', function (Blueprint $table) { $table->id();
$table->id(); $table->string('server_id', 64)->index();
$table->string('server_id', 64)->index(); $table->string('tool_name', 128);
$table->string('tool_name', 128); $table->string('version', 32); // semver: 1.0.0, 2.1.0-beta, etc.
$table->string('version', 32); // semver: 1.0.0, 2.1.0-beta, etc. $table->json('input_schema')->nullable();
$table->json('input_schema')->nullable(); $table->json('output_schema')->nullable();
$table->json('output_schema')->nullable(); $table->text('description')->nullable();
$table->text('description')->nullable(); $table->text('changelog')->nullable();
$table->text('changelog')->nullable(); $table->text('migration_notes')->nullable(); // guidance for upgrading from previous version
$table->text('migration_notes')->nullable(); // guidance for upgrading from previous version $table->boolean('is_latest')->default(false);
$table->boolean('is_latest')->default(false); $table->timestamp('deprecated_at')->nullable();
$table->timestamp('deprecated_at')->nullable(); $table->timestamp('sunset_at')->nullable(); // after this date, version is blocked
$table->timestamp('sunset_at')->nullable(); // after this date, version is blocked $table->timestamps();
$table->timestamps();
// Unique constraint: one version per tool per server // Unique constraint: one version per tool per server
$table->unique(['server_id', 'tool_name', 'version'], 'mcp_tool_versions_unique'); $table->unique(['server_id', 'tool_name', 'version'], 'mcp_tool_versions_unique');
// Index for finding latest versions // Index for finding latest versions
$table->index(['server_id', 'tool_name', 'is_latest'], 'mcp_tool_versions_latest'); $table->index(['server_id', 'tool_name', 'is_latest'], 'mcp_tool_versions_latest');
// Index for finding deprecated/sunset versions // Index for finding deprecated/sunset versions
$table->index(['deprecated_at', 'sunset_at'], 'mcp_tool_versions_lifecycle'); $table->index(['deprecated_at', 'sunset_at'], 'mcp_tool_versions_lifecycle');
}); });
}
} }
public function down(): void public function down(): void