diff --git a/README.md b/README.md
index 16c534863..b836a1f6c 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,15 @@ and set it as the GITHUB_PERSONAL_ACCESS_TOKEN environment variable.
   - `repo`: Repository name (string, required)
   - `issue_number`: Issue number (number, required)
 
+- **create_issue** - Create a new issue in a GitHub repository
+
+  - `owner`: Repository owner (string, required)
+  - `repo`: Repository name (string, required)
+  - `title`: Issue title (string, required)
+  - `body`: Issue body content (string, optional)
+  - `assignees`: Comma-separated list of usernames to assign to this issue (string, optional)
+  - `labels`: Comma-separated list of labels to apply to this issue (string, optional)
+
 - **add_issue_comment** - Add a comment to an issue
 
   - `owner`: Repository owner (string, required)
@@ -313,16 +322,14 @@ Lots of things!
 Missing tools:
 
 - push_files (files array)
-- create_issue (assignees and labels arrays)
 - list_issues (labels array)
 - update_issue (labels and assignees arrays)
 - create_pull_request_review (comments array)
 
 Testing
 
-- Unit tests
 - Integration tests
-- Blackbox testing: ideally comparing output to Anthromorphic's server to make sure that this is a fully compatible drop-in replacement.
+- Blackbox testing: ideally comparing output to Anthropic's server to make sure that this is a fully compatible drop-in replacement.
 
 And some other stuff:
 
diff --git a/pkg/github/issues.go b/pkg/github/issues.go
index 67179dec5..3a23825ba 100644
--- a/pkg/github/issues.go
+++ b/pkg/github/issues.go
@@ -182,3 +182,83 @@ func searchIssues(client *github.Client) (tool mcp.Tool, handler server.ToolHand
 			return mcp.NewToolResultText(string(r)), nil
 		}
 }
+
+// createIssue creates a tool to create a new issue in a GitHub repository.
+func createIssue(client *github.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
+	return mcp.NewTool("create_issue",
+			mcp.WithDescription("Create a new issue in a GitHub repository"),
+			mcp.WithString("owner",
+				mcp.Required(),
+				mcp.Description("Repository owner"),
+			),
+			mcp.WithString("repo",
+				mcp.Required(),
+				mcp.Description("Repository name"),
+			),
+			mcp.WithString("title",
+				mcp.Required(),
+				mcp.Description("Issue title"),
+			),
+			mcp.WithString("body",
+				mcp.Description("Issue body content"),
+			),
+			mcp.WithString("assignees",
+				mcp.Description("Comma-separate list of usernames to assign to this issue"),
+			),
+			mcp.WithString("labels",
+				mcp.Description("Comma-separate list of labels to apply to this issue"),
+			),
+		),
+		func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+			owner := request.Params.Arguments["owner"].(string)
+			repo := request.Params.Arguments["repo"].(string)
+			title := request.Params.Arguments["title"].(string)
+
+			// Optional parameters
+			var body string
+			if b, ok := request.Params.Arguments["body"].(string); ok {
+				body = b
+			}
+
+			// Parse assignees if present
+			assignees := []string{} // default to empty slice, can't be nil
+			if a, ok := request.Params.Arguments["assignees"].(string); ok && a != "" {
+				assignees = parseCommaSeparatedList(a)
+			}
+
+			// Parse labels if present
+			labels := []string{} // default to empty slice, can't be nil
+			if l, ok := request.Params.Arguments["labels"].(string); ok && l != "" {
+				labels = parseCommaSeparatedList(l)
+			}
+
+			// Create the issue request
+			issueRequest := &github.IssueRequest{
+				Title:     github.Ptr(title),
+				Body:      github.Ptr(body),
+				Assignees: &assignees,
+				Labels:    &labels,
+			}
+
+			issue, resp, err := client.Issues.Create(ctx, owner, repo, issueRequest)
+			if err != nil {
+				return nil, fmt.Errorf("failed to create issue: %w", err)
+			}
+			defer func() { _ = resp.Body.Close() }()
+
+			if resp.StatusCode != http.StatusCreated {
+				body, err := io.ReadAll(resp.Body)
+				if err != nil {
+					return nil, fmt.Errorf("failed to read response body: %w", err)
+				}
+				return mcp.NewToolResultError(fmt.Sprintf("failed to create issue: %s", string(body))), nil
+			}
+
+			r, err := json.Marshal(issue)
+			if err != nil {
+				return nil, fmt.Errorf("failed to marshal response: %w", err)
+			}
+
+			return mcp.NewToolResultText(string(r)), nil
+		}
+}
diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go
index 7e9944b35..c1ebf6d01 100644
--- a/pkg/github/issues_test.go
+++ b/pkg/github/issues_test.go
@@ -369,3 +369,158 @@ func Test_SearchIssues(t *testing.T) {
 		})
 	}
 }
+
+func Test_CreateIssue(t *testing.T) {
+	// Verify tool definition once
+	mockClient := github.NewClient(nil)
+	tool, _ := createIssue(mockClient)
+
+	assert.Equal(t, "create_issue", tool.Name)
+	assert.NotEmpty(t, tool.Description)
+	assert.Contains(t, tool.InputSchema.Properties, "owner")
+	assert.Contains(t, tool.InputSchema.Properties, "repo")
+	assert.Contains(t, tool.InputSchema.Properties, "title")
+	assert.Contains(t, tool.InputSchema.Properties, "body")
+	assert.Contains(t, tool.InputSchema.Properties, "assignees")
+	assert.Contains(t, tool.InputSchema.Properties, "labels")
+	assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "title"})
+
+	// Setup mock issue for success case
+	mockIssue := &github.Issue{
+		Number:    github.Ptr(123),
+		Title:     github.Ptr("Test Issue"),
+		Body:      github.Ptr("This is a test issue"),
+		State:     github.Ptr("open"),
+		HTMLURL:   github.Ptr("https://github.com/owner/repo/issues/123"),
+		Assignees: []*github.User{{Login: github.Ptr("user1")}, {Login: github.Ptr("user2")}},
+		Labels:    []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("help wanted")}},
+	}
+
+	tests := []struct {
+		name           string
+		mockedClient   *http.Client
+		requestArgs    map[string]interface{}
+		expectError    bool
+		expectedIssue  *github.Issue
+		expectedErrMsg string
+	}{
+		{
+			name: "successful issue creation with all fields",
+			mockedClient: mock.NewMockedHTTPClient(
+				mock.WithRequestMatchHandler(
+					mock.PostReposIssuesByOwnerByRepo,
+					mockResponse(t, http.StatusCreated, mockIssue),
+				),
+			),
+			requestArgs: map[string]interface{}{
+				"owner":     "owner",
+				"repo":      "repo",
+				"title":     "Test Issue",
+				"body":      "This is a test issue",
+				"assignees": []interface{}{"user1", "user2"},
+				"labels":    []interface{}{"bug", "help wanted"},
+			},
+			expectError:   false,
+			expectedIssue: mockIssue,
+		},
+		{
+			name: "successful issue creation with minimal fields",
+			mockedClient: mock.NewMockedHTTPClient(
+				mock.WithRequestMatchHandler(
+					mock.PostReposIssuesByOwnerByRepo,
+					mockResponse(t, http.StatusCreated, &github.Issue{
+						Number:  github.Ptr(124),
+						Title:   github.Ptr("Minimal Issue"),
+						HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"),
+						State:   github.Ptr("open"),
+					}),
+				),
+			),
+			requestArgs: map[string]interface{}{
+				"owner": "owner",
+				"repo":  "repo",
+				"title": "Minimal Issue",
+			},
+			expectError: false,
+			expectedIssue: &github.Issue{
+				Number:  github.Ptr(124),
+				Title:   github.Ptr("Minimal Issue"),
+				HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"),
+				State:   github.Ptr("open"),
+			},
+		},
+		{
+			name: "issue creation fails",
+			mockedClient: mock.NewMockedHTTPClient(
+				mock.WithRequestMatchHandler(
+					mock.PostReposIssuesByOwnerByRepo,
+					http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+						w.WriteHeader(http.StatusUnprocessableEntity)
+						_, _ = w.Write([]byte(`{"message": "Validation failed"}`))
+					}),
+				),
+			),
+			requestArgs: map[string]interface{}{
+				"owner": "owner",
+				"repo":  "repo",
+				"title": "",
+			},
+			expectError:    true,
+			expectedErrMsg: "failed to create issue",
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			// Setup client with mock
+			client := github.NewClient(tc.mockedClient)
+			_, handler := createIssue(client)
+
+			// Create call request
+			request := createMCPRequest(tc.requestArgs)
+
+			// Call handler
+			result, err := handler(context.Background(), request)
+
+			// Verify results
+			if tc.expectError {
+				require.Error(t, err)
+				assert.Contains(t, err.Error(), tc.expectedErrMsg)
+				return
+			}
+
+			require.NoError(t, err)
+			textContent := getTextResult(t, result)
+
+			// Unmarshal and verify the result
+			var returnedIssue github.Issue
+			err = json.Unmarshal([]byte(textContent.Text), &returnedIssue)
+			require.NoError(t, err)
+
+			assert.Equal(t, *tc.expectedIssue.Number, *returnedIssue.Number)
+			assert.Equal(t, *tc.expectedIssue.Title, *returnedIssue.Title)
+			assert.Equal(t, *tc.expectedIssue.State, *returnedIssue.State)
+			assert.Equal(t, *tc.expectedIssue.HTMLURL, *returnedIssue.HTMLURL)
+
+			if tc.expectedIssue.Body != nil {
+				assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body)
+			}
+
+			// Check assignees if expected
+			if len(tc.expectedIssue.Assignees) > 0 {
+				assert.Equal(t, len(tc.expectedIssue.Assignees), len(returnedIssue.Assignees))
+				for i, assignee := range returnedIssue.Assignees {
+					assert.Equal(t, *tc.expectedIssue.Assignees[i].Login, *assignee.Login)
+				}
+			}
+
+			// Check labels if expected
+			if len(tc.expectedIssue.Labels) > 0 {
+				assert.Equal(t, len(tc.expectedIssue.Labels), len(returnedIssue.Labels))
+				for i, label := range returnedIssue.Labels {
+					assert.Equal(t, *tc.expectedIssue.Labels[i].Name, *label.Name)
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/github/server.go b/pkg/github/server.go
index 30d9b3bc2..31521fb87 100644
--- a/pkg/github/server.go
+++ b/pkg/github/server.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"strings"
 
 	"github.com/google/go-github/v69/github"
 	"github.com/mark3labs/mcp-go/mcp"
@@ -34,6 +35,7 @@ func NewServer(client *github.Client) *server.MCPServer {
 	// Add GitHub tools - Issues
 	s.AddTool(getIssue(client))
 	s.AddTool(addIssueComment(client))
+	s.AddTool(createIssue(client))
 	s.AddTool(searchIssues(client))
 
 	// Add GitHub tools - Pull Requests
@@ -106,3 +108,22 @@ func isAcceptedError(err error) bool {
 	var acceptedError *github.AcceptedError
 	return errors.As(err, &acceptedError)
 }
+
+// parseCommaSeparatedList is a helper function that parses a comma-separated list of strings from the input string.
+func parseCommaSeparatedList(input string) []string {
+	if input == "" {
+		return nil
+	}
+
+	parts := strings.Split(input, ",")
+	result := make([]string, 0, len(parts))
+
+	for _, part := range parts {
+		trimmed := strings.TrimSpace(part)
+		if trimmed != "" {
+			result = append(result, trimmed)
+		}
+	}
+
+	return result
+}
diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go
index d56993ded..5515c8814 100644
--- a/pkg/github/server_test.go
+++ b/pkg/github/server_test.go
@@ -166,3 +166,64 @@ func Test_IsAcceptedError(t *testing.T) {
 		})
 	}
 }
+
+func Test_ParseCommaSeparatedList(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    string
+		expected []string
+	}{
+		{
+			name:     "simple comma separated values",
+			input:    "one,two,three",
+			expected: []string{"one", "two", "three"},
+		},
+		{
+			name:     "values with spaces",
+			input:    "one, two, three",
+			expected: []string{"one", "two", "three"},
+		},
+		{
+			name:     "values with extra spaces",
+			input:    "  one  ,  two  ,  three  ",
+			expected: []string{"one", "two", "three"},
+		},
+		{
+			name:     "empty values in between",
+			input:    "one,,three",
+			expected: []string{"one", "three"},
+		},
+		{
+			name:     "only spaces",
+			input:    " , , ",
+			expected: []string{},
+		},
+		{
+			name:     "empty string",
+			input:    "",
+			expected: nil,
+		},
+		{
+			name:     "single value",
+			input:    "one",
+			expected: []string{"one"},
+		},
+		{
+			name:     "trailing comma",
+			input:    "one,two,",
+			expected: []string{"one", "two"},
+		},
+		{
+			name:     "leading comma",
+			input:    ",one,two",
+			expected: []string{"one", "two"},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			result := parseCommaSeparatedList(tc.input)
+			assert.Equal(t, tc.expected, result)
+		})
+	}
+}